@promptbook/wizard 0.112.0-98 → 0.112.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 (156) hide show
  1. package/README.md +37 -21
  2. package/esm/index.es.js +1700 -103
  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 +0 -2
  6. package/esm/src/_packages/node.index.d.ts +40 -0
  7. package/esm/src/_packages/types.index.d.ts +18 -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-3.0/BookNodeAgentSource.d.ts +38 -0
  13. package/esm/src/book-3.0/CliAgent.d.ts +66 -0
  14. package/esm/src/book-3.0/CliAgent.test.d.ts +1 -0
  15. package/esm/src/book-3.0/LiteAgent.d.ts +68 -0
  16. package/esm/src/book-3.0/LiteAgent.test.d.ts +1 -0
  17. package/esm/src/book-3.0/agentFolderPaths.d.ts +30 -0
  18. package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
  19. package/esm/src/book-components/BookEditor/BookEditor.d.ts +6 -5
  20. package/esm/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
  21. package/esm/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
  22. package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
  23. package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
  24. package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  25. package/esm/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
  26. package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  27. package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  28. package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  29. package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  30. package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  31. package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  32. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  33. package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  34. package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  35. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  36. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  37. package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  38. package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  39. package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  40. package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  41. package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  42. package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  43. package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  44. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  45. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  46. package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
  47. package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  48. package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  49. package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  50. package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
  51. package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
  52. package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
  53. package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  54. package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  55. package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
  56. package/esm/src/cli/cli-commands/agent/chat.d.ts +10 -0
  57. package/esm/src/cli/cli-commands/agent/exec.d.ts +10 -0
  58. package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  59. package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
  60. package/esm/src/cli/cli-commands/agent.d.ts +14 -0
  61. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +17 -1
  62. package/esm/src/cli/cli-commands/agents-server/run.d.ts +6 -0
  63. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
  64. package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  65. package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
  66. package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  67. package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
  68. package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
  69. package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
  70. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
  71. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
  72. package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
  73. package/esm/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
  74. package/esm/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
  75. package/esm/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
  76. package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -0
  77. package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  78. package/esm/src/version.d.ts +1 -1
  79. package/package.json +2 -2
  80. package/umd/index.umd.js +1701 -101
  81. package/umd/index.umd.js.map +1 -1
  82. package/umd/src/_packages/components.index.d.ts +4 -0
  83. package/umd/src/_packages/core.index.d.ts +0 -2
  84. package/umd/src/_packages/node.index.d.ts +40 -0
  85. package/umd/src/_packages/types.index.d.ts +18 -0
  86. package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  87. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  88. package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
  89. package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  90. package/umd/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
  91. package/umd/src/book-3.0/CliAgent.d.ts +66 -0
  92. package/umd/src/book-3.0/CliAgent.test.d.ts +1 -0
  93. package/umd/src/book-3.0/LiteAgent.d.ts +68 -0
  94. package/umd/src/book-3.0/LiteAgent.test.d.ts +1 -0
  95. package/umd/src/book-3.0/agentFolderPaths.d.ts +30 -0
  96. package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
  97. package/umd/src/book-components/BookEditor/BookEditor.d.ts +6 -5
  98. package/umd/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
  99. package/umd/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
  100. package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
  101. package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
  102. package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  103. package/umd/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
  104. package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  105. package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  106. package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  107. package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  108. package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  109. package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  110. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  111. package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  112. package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  113. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  114. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  115. package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  116. package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  117. package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  118. package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  119. package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  120. package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  121. package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  122. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  123. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  124. package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
  125. package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  126. package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  127. package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  128. package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
  129. package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
  130. package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
  131. package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  132. package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  133. package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
  134. package/umd/src/cli/cli-commands/agent/chat.d.ts +10 -0
  135. package/umd/src/cli/cli-commands/agent/exec.d.ts +10 -0
  136. package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  137. package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
  138. package/umd/src/cli/cli-commands/agent.d.ts +14 -0
  139. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +17 -1
  140. package/umd/src/cli/cli-commands/agents-server/run.d.ts +6 -0
  141. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
  142. package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  143. package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
  144. package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  145. package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
  146. package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
  147. package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
  148. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
  149. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
  150. package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
  151. package/umd/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
  152. package/umd/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
  153. package/umd/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
  154. package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -0
  155. package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  156. 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('socket.io-client'), require('spacetrim'), require('@anthropic-ai/sdk'), require('bottleneck'), require('colors'), require('@azure/openai'), require('fs/promises'), require('child_process'), require('waitasecond'), require('crypto-js'), require('crypto-js/enc-hex'), require('path'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('dotenv'), require('crypto-js/sha256'), require('jszip'), require('rxjs'), require('crypto'), require('moment'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
3
3
  typeof define === 'function' && define.amd ? define(['exports', 'socket.io-client', 'spacetrim', '@anthropic-ai/sdk', 'bottleneck', 'colors', '@azure/openai', 'fs/promises', 'child_process', 'waitasecond', 'crypto-js', 'crypto-js/enc-hex', 'path', '@mozilla/readability', 'jsdom', 'showdown', 'dotenv', 'crypto-js/sha256', 'jszip', 'rxjs', 'crypto', 'moment', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-wizard"] = {}, global.socket_ioClient, global._spaceTrim, global.Anthropic, global.Bottleneck, global.colors, global.openai, global.promises, global.child_process, global.waitasecond, global.cryptoJs, global.hexEncoder, global.path, global.readability, global.jsdom, global.showdown, global.dotenv, global.sha256, global.JSZip, global.rxjs, global.crypto, global.moment, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
5
- })(this, (function (exports, socket_ioClient, _spaceTrim, Anthropic, Bottleneck, colors, openai, promises, child_process, waitasecond, cryptoJs, hexEncoder, path, readability, jsdom, showdown, dotenv, sha256, JSZip, rxjs, crypto, moment, mimeTypes, papaparse, agents, OpenAI) { 'use strict';
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-wizard"] = {}, global.socket_ioClient, global._spaceTrim, global.Anthropic, global.Bottleneck, global.colors, global.openai, global.promises, global.child_process, global.waitasecond, global.CryptoJS, global.hexEncoder, global.path, global.readability, global.jsdom, global.showdown, global.dotenv, global.sha256, global.JSZip, global.rxjs, null, global.moment, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
5
+ })(this, (function (exports, socket_ioClient, _spaceTrim, Anthropic, Bottleneck, colors, openai, promises, child_process, waitasecond, CryptoJS, hexEncoder, path, readability, jsdom, showdown, dotenv, sha256, JSZip, rxjs, crypto, moment, mimeTypes, papaparse, agents, 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 Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
29
29
  var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
30
30
  var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
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 dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
33
35
  var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
34
36
  var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
35
37
  var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
38
+ var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
36
39
  var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
37
40
 
38
41
  // ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
@@ -49,7 +52,7 @@
49
52
  * @generated
50
53
  * @see https://github.com/webgptorg/promptbook
51
54
  */
52
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-98';
55
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0';
53
56
  /**
54
57
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
55
58
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2104,7 +2107,7 @@
2104
2107
  title: 'Anthropic Claude',
2105
2108
  packageName: '@promptbook/anthropic-claude',
2106
2109
  className: 'AnthropicClaudeExecutionTools',
2107
- envVariables: ['ANTHROPIC_CLAUDE_API_KEY'],
2110
+ envVariables: ['ANTHROPIC_API_KEY', 'ANTHROPIC_CLAUDE_API_KEY'],
2108
2111
  trustLevel: 'CLOSED',
2109
2112
  order: MODEL_ORDERS.TOP_TIER,
2110
2113
  getBoilerplateConfiguration() {
@@ -2121,14 +2124,17 @@
2121
2124
  };
2122
2125
  },
2123
2126
  createConfigurationFromEnv(env) {
2127
+ var _a;
2124
2128
  // Note: Note using `process.env` BUT `env` to pass in the environment variables dynamically
2125
- if (typeof env.ANTHROPIC_CLAUDE_API_KEY === 'string') {
2129
+ // Note: `ANTHROPIC_API_KEY` takes precedence over the deprecated `ANTHROPIC_CLAUDE_API_KEY`
2130
+ const apiKey = (_a = env.ANTHROPIC_API_KEY) !== null && _a !== void 0 ? _a : env.ANTHROPIC_CLAUDE_API_KEY;
2131
+ if (typeof apiKey === 'string') {
2126
2132
  return {
2127
2133
  title: 'Claude (from env)',
2128
2134
  packageName: '@promptbook/anthropic-claude',
2129
2135
  className: 'AnthropicClaudeExecutionTools',
2130
2136
  options: {
2131
- apiKey: env.ANTHROPIC_CLAUDE_API_KEY,
2137
+ apiKey,
2132
2138
  },
2133
2139
  };
2134
2140
  }
@@ -2357,7 +2363,7 @@
2357
2363
  * @private internal helper function
2358
2364
  */
2359
2365
  function $randomToken(randomness) {
2360
- return crypto.randomBytes(randomness).toString('hex');
2366
+ return CryptoJS__default["default"].lib.WordArray.random(randomness).toString(CryptoJS__default["default"].enc.Hex);
2361
2367
  }
2362
2368
  // TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
2363
2369
  // TODO: Maybe use nanoid instead https://github.com/ai/nanoid
@@ -10031,7 +10037,7 @@
10031
10037
  return { skippedReason: 'invalid_data_url' };
10032
10038
  }
10033
10039
  return {
10034
- file: new File([parsed.buffer], parsed.filename, {
10040
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
10035
10041
  type: parsed.mimeType,
10036
10042
  }),
10037
10043
  sizeBytes: parsed.buffer.length,
@@ -13312,7 +13318,7 @@
13312
13318
  * @public exported from `@promptbook/editable`
13313
13319
  */
13314
13320
  function knowledgeSourceContentToName(knowledgeSourceContent) {
13315
- const hash = cryptoJs.SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
13321
+ const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
13316
13322
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
13317
13323
  .toString( /* hex */)
13318
13324
  .substring(0, 20);
@@ -14192,7 +14198,7 @@
14192
14198
  console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
14193
14199
  value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14194
14200
  }
14195
- const csv = papaparse.parse(value, settings);
14201
+ const csv = papaparse__default["default"].parse(value, settings);
14196
14202
  return csv;
14197
14203
  }
14198
14204
 
@@ -14277,10 +14283,10 @@
14277
14283
  i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
14278
14284
  );
14279
14285
  */
14280
- await onProgress(papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
14286
+ await onProgress(papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
14281
14287
  }
14282
14288
  }
14283
- return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14289
+ return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14284
14290
  },
14285
14291
  },
14286
14292
  {
@@ -14308,7 +14314,7 @@
14308
14314
  return /* not await */ mapCallback({ [key]: value }, index, array.length);
14309
14315
  }));
14310
14316
  }));
14311
- return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14317
+ return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14312
14318
  },
14313
14319
  },
14314
14320
  ],
@@ -16324,7 +16330,7 @@
16324
16330
  const { filename: sourceFilename, url } = source;
16325
16331
  const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
16326
16332
  // TODO: [👬] DRY
16327
- const hash = cryptoJs.SHA256(
16333
+ const hash = CryptoJS__default["default"].SHA256(
16328
16334
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
16329
16335
  hexEncoder__default["default"].parse(sourceFilename || url || 'untitled'))
16330
16336
  .toString( /* hex */)
@@ -17227,7 +17233,7 @@
17227
17233
  * @private utility of `WebsiteScraper`
17228
17234
  */
17229
17235
  function createShowdownConverter() {
17230
- return new showdown.Converter({
17236
+ return new showdown__default["default"].Converter({
17231
17237
  flavor: 'github',
17232
17238
  /*
17233
17239
  > import showdownHighlight from 'showdown-highlight';
@@ -17411,7 +17417,7 @@
17411
17417
  * @public exported from `@promptbook/utils`
17412
17418
  */
17413
17419
  function computeHash(value) {
17414
- return cryptoJs.SHA256(hexEncoder__default["default"].parse(_spaceTrim.spaceTrim(valueToString(value)))).toString( /* hex */);
17420
+ return CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(_spaceTrim.spaceTrim(valueToString(value)))).toString( /* hex */);
17415
17421
  }
17416
17422
  // TODO: [🥬][🥬] Use this ACRY
17417
17423
 
@@ -19180,6 +19186,11 @@
19180
19186
  * - `isValidAgentUrl` *(this one)* which tests just agent URL
19181
19187
  * - `isValidPipelineUrl` which tests just pipeline URL
19182
19188
  *
19189
+ * Note: This is a pure structural validator and does not block private/internal network
19190
+ * addresses. Callers that fetch the URL server-side from an untrusted network context must
19191
+ * additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
19192
+ * outbound request.
19193
+ *
19183
19194
  * @public exported from `@promptbook/utils`
19184
19195
  */
19185
19196
  function isValidAgentUrl(url) {
@@ -19193,12 +19204,6 @@
19193
19204
  // TODO: [🐠]
19194
19205
  return false;
19195
19206
  }
19196
- /*
19197
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
19198
- if (isUrlOnPrivateNetwork(url)) {
19199
- return false;
19200
- }
19201
- */
19202
19207
  return true;
19203
19208
  }
19204
19209
  // TODO: [🐠] Maybe more info why the URL is invalid
@@ -19374,8 +19379,11 @@
19374
19379
  if (!trimmedContent) {
19375
19380
  return requirements;
19376
19381
  }
19377
- // Add goal as a proper h2 section to the system message
19378
- const goalSection = `## Goal\n\n${trimmedContent}`;
19382
+ const goalSection = _spaceTrim.spaceTrim((block) => `
19383
+ ## Goal
19384
+
19385
+ ${block(trimmedContent)}
19386
+ `);
19379
19387
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
19380
19388
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
19381
19389
  }
@@ -19761,8 +19769,11 @@
19761
19769
  if (!trimmedContent) {
19762
19770
  return requirements;
19763
19771
  }
19764
- // Add language as a bullet under a ## Language section
19765
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
19772
+ const languageSection = _spaceTrim.spaceTrim((block) => `
19773
+ ## Language
19774
+
19775
+ - Your language is ${block(trimmedContent)}
19776
+ `);
19766
19777
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
19767
19778
  }
19768
19779
  }
@@ -22105,21 +22116,22 @@
22105
22116
  * @private helper of `fractalAvatarVisual`
22106
22117
  */
22107
22118
  function drawDragonCurveLayer(context, points, options) {
22108
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
22119
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
22109
22120
  const firstPoint = points[0];
22110
22121
  const lastPoint = points[points.length - 1];
22111
22122
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
22112
22123
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
22113
22124
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
22114
22125
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
22126
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
22127
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
22115
22128
  context.save();
22116
22129
  context.beginPath();
22117
22130
  tracePolyline(context, points);
22118
- context.strokeStyle = `${shadowColor}82`;
22119
- context.lineWidth = strokeWidth * 1.8;
22131
+ context.strokeStyle = `${shadowColor}48`;
22132
+ context.lineWidth = strokeWidth * 4.5;
22120
22133
  context.lineJoin = 'round';
22121
22134
  context.lineCap = 'round';
22122
- context.filter = `blur(${size * 0.022}px)`;
22123
22135
  context.stroke();
22124
22136
  context.restore();
22125
22137
  context.beginPath();
@@ -22581,7 +22593,7 @@
22581
22593
  *
22582
22594
  * @private helper of `minecraft2AvatarVisual`
22583
22595
  */
22584
- const LIGHT_DIRECTION$2 = normalizeVector3({
22596
+ const LIGHT_DIRECTION$4 = normalizeVector3({
22585
22597
  x: 0.4,
22586
22598
  y: -0.65,
22587
22599
  z: 0.92,
@@ -22703,11 +22715,23 @@
22703
22715
  * @private helper of `minecraft2AvatarVisual`
22704
22716
  */
22705
22717
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
22718
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
22719
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
22720
+ const rx = size * (0.16 + interaction.intensity * 0.015);
22721
+ const ry = size * 0.055;
22722
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
22706
22723
  context.save();
22707
- context.fillStyle = `${palette.shadow}66`;
22708
- context.filter = `blur(${size * 0.02}px)`;
22724
+ context.translate(cx, cy);
22725
+ context.scale(1, ry / rx);
22726
+ const blurRadius = rx * 1.4;
22727
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
22728
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
22729
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
22730
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
22731
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
22732
+ context.fillStyle = shadowGradient;
22709
22733
  context.beginPath();
22710
- 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);
22734
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
22711
22735
  context.fill();
22712
22736
  context.restore();
22713
22737
  }
@@ -22793,7 +22817,7 @@
22793
22817
  corners: projectedCorners,
22794
22818
  texture: faceDefinition.texture,
22795
22819
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
22796
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
22820
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
22797
22821
  outlineColor: cuboid.outlineColor,
22798
22822
  };
22799
22823
  });
@@ -22931,13 +22955,27 @@
22931
22955
  spotlight.addColorStop(1, `${palette.highlight}00`);
22932
22956
  context.fillStyle = spotlight;
22933
22957
  context.fillRect(0, 0, size, size);
22934
- context.save();
22935
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
22936
- context.filter = `blur(${size * 0.018}px)`;
22937
- context.beginPath();
22938
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
22939
- context.fill();
22940
- context.restore();
22958
+ {
22959
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
22960
+ const cx = size * 0.5;
22961
+ const cy = size * 0.86;
22962
+ const rx = size * 0.2;
22963
+ const ry = size * 0.06;
22964
+ const blurRadius = rx * 1.4;
22965
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
22966
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
22967
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
22968
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
22969
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
22970
+ context.save();
22971
+ context.translate(cx, cy);
22972
+ context.scale(1, ry / rx);
22973
+ context.fillStyle = shadowGradient;
22974
+ context.beginPath();
22975
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
22976
+ context.fill();
22977
+ context.restore();
22978
+ }
22941
22979
  drawVoxelCuboid(context, {
22942
22980
  x: bodyX,
22943
22981
  y: bodyY,
@@ -23466,7 +23504,7 @@
23466
23504
  *
23467
23505
  * @private helper of `octopus3AvatarVisual`
23468
23506
  */
23469
- function formatAlphaHex$1(opacity) {
23507
+ function formatAlphaHex$2(opacity) {
23470
23508
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
23471
23509
  .toString(16)
23472
23510
  .padStart(2, '0');
@@ -23845,7 +23883,7 @@
23845
23883
  context.beginPath();
23846
23884
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
23847
23885
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
23848
- context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
23886
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
23849
23887
  context.lineWidth = radiusX * 0.08;
23850
23888
  context.lineCap = 'round';
23851
23889
  context.stroke();
@@ -23926,7 +23964,7 @@
23926
23964
  context.beginPath();
23927
23965
  context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23928
23966
  context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23929
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
23967
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
23930
23968
  context.lineWidth = projectedRadiusX * 0.08;
23931
23969
  context.lineCap = 'round';
23932
23970
  context.stroke();
@@ -23972,7 +24010,7 @@
23972
24010
  *
23973
24011
  * @private helper of the 3D octopus avatar visuals
23974
24012
  */
23975
- function formatAlphaHex(opacity) {
24013
+ function formatAlphaHex$1(opacity) {
23976
24014
  return Math.round(clampNumber$1(opacity, 0, 1) * 255)
23977
24015
  .toString(16)
23978
24016
  .padStart(2, '0');
@@ -23984,11 +24022,40 @@
23984
24022
  *
23985
24023
  * @private helper of `octopus3dAvatarVisual`
23986
24024
  */
23987
- const LIGHT_DIRECTION$1 = normalizeVector3({
24025
+ const LIGHT_DIRECTION$3 = normalizeVector3({
23988
24026
  x: 0.48,
23989
24027
  y: -0.62,
23990
24028
  z: 0.94,
23991
24029
  });
24030
+ /**
24031
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
24032
+ *
24033
+ * @private helper of `octopus3dAvatarVisual`
24034
+ */
24035
+ const octopus3dStableStateCache = new WeakMap();
24036
+ /**
24037
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
24038
+ *
24039
+ * @private helper of `octopus3dAvatarVisual`
24040
+ */
24041
+ function getOctopus3dStableState(createRandom) {
24042
+ const cached = octopus3dStableStateCache.get(createRandom);
24043
+ if (cached !== undefined) {
24044
+ return cached;
24045
+ }
24046
+ const animationRandom = createRandom('octopus3d-animation-profile');
24047
+ const eyeRandom = createRandom('octopus3d-eye-profile');
24048
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
24049
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
24050
+ const state = {
24051
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
24052
+ animationPhase: animationRandom() * Math.PI * 2,
24053
+ leftEyePhaseOffset,
24054
+ rightEyePhaseOffset,
24055
+ };
24056
+ octopus3dStableStateCache.set(createRandom, state);
24057
+ return state;
24058
+ }
23992
24059
  /**
23993
24060
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
23994
24061
  *
@@ -24001,10 +24068,7 @@
24001
24068
  isAnimated: true,
24002
24069
  supportsPointerTracking: true,
24003
24070
  render({ context, size, palette, createRandom, timeMs, interaction }) {
24004
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24005
- const animationRandom = createRandom('octopus3d-animation-profile');
24006
- const eyeRandom = createRandom('octopus3d-eye-profile');
24007
- const animationPhase = animationRandom() * Math.PI * 2;
24071
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
24008
24072
  const sceneCenterX = size * 0.5;
24009
24073
  const sceneCenterY = size * 0.56;
24010
24074
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -24101,12 +24165,12 @@
24101
24165
  x: -faceEyeSpacing,
24102
24166
  y: faceEyeYOffset,
24103
24167
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
24104
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
24168
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24105
24169
  drawProjectedOrganicEye(context, {
24106
24170
  x: faceEyeSpacing,
24107
24171
  y: faceEyeYOffset,
24108
24172
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
24109
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
24173
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24110
24174
  drawProjectedOrganicMouth(context, [
24111
24175
  {
24112
24176
  x: -mouthHalfWidth,
@@ -24150,14 +24214,28 @@
24150
24214
  /**
24151
24215
  * Draws the soft ground shadow below the octopus.
24152
24216
  *
24217
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24218
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24219
+ *
24153
24220
  * @private helper of `octopus3dAvatarVisual`
24154
24221
  */
24155
24222
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
24223
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
24224
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
24225
+ const rx = size * (0.18 + interaction.intensity * 0.02);
24226
+ const ry = size * 0.06;
24156
24227
  context.save();
24157
- context.fillStyle = `${palette.shadow}66`;
24158
- context.filter = `blur(${size * 0.022}px)`;
24228
+ context.translate(cx, cy);
24229
+ context.scale(1, ry / rx);
24230
+ const blurRadius = rx * 1.4;
24231
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
24232
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
24233
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
24234
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
24235
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
24236
+ context.fillStyle = shadowGradient;
24159
24237
  context.beginPath();
24160
- 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);
24238
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
24161
24239
  context.fill();
24162
24240
  context.restore();
24163
24241
  }
@@ -24194,7 +24272,7 @@
24194
24272
  corners: projectedCorners,
24195
24273
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
24196
24274
  transformedCorners.length,
24197
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
24275
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
24198
24276
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
24199
24277
  outlineColor,
24200
24278
  });
@@ -24383,11 +24461,40 @@
24383
24461
  *
24384
24462
  * @private helper of `octopus3d2AvatarVisual`
24385
24463
  */
24386
- const LIGHT_DIRECTION = normalizeVector3({
24464
+ const LIGHT_DIRECTION$2 = normalizeVector3({
24387
24465
  x: 0.38,
24388
24466
  y: -0.6,
24389
24467
  z: 0.98,
24390
24468
  });
24469
+ /**
24470
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
24471
+ *
24472
+ * @private helper of `octopus3d2AvatarVisual`
24473
+ */
24474
+ const octopus3d2StableStateCache = new WeakMap();
24475
+ /**
24476
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
24477
+ *
24478
+ * @private helper of `octopus3d2AvatarVisual`
24479
+ */
24480
+ function getOctopus3d2StableState(createRandom) {
24481
+ const cached = octopus3d2StableStateCache.get(createRandom);
24482
+ if (cached !== undefined) {
24483
+ return cached;
24484
+ }
24485
+ const animationRandom = createRandom('octopus3d2-animation-profile');
24486
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
24487
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
24488
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
24489
+ const state = {
24490
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
24491
+ animationPhase: animationRandom() * Math.PI * 2,
24492
+ leftEyePhaseOffset,
24493
+ rightEyePhaseOffset,
24494
+ };
24495
+ octopus3d2StableStateCache.set(createRandom, state);
24496
+ return state;
24497
+ }
24391
24498
  /**
24392
24499
  * Octopus 3D 2 avatar visual.
24393
24500
  *
@@ -24400,10 +24507,7 @@
24400
24507
  isAnimated: true,
24401
24508
  supportsPointerTracking: true,
24402
24509
  render({ context, size, palette, createRandom, timeMs, interaction }) {
24403
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24404
- const animationRandom = createRandom('octopus3d2-animation-profile');
24405
- const eyeRandom = createRandom('octopus3d2-eye-profile');
24406
- const animationPhase = animationRandom() * Math.PI * 2;
24510
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
24407
24511
  const sceneCenterX = size * 0.5;
24408
24512
  const sceneCenterY = size * 0.575;
24409
24513
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -24456,8 +24560,8 @@
24456
24560
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
24457
24561
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
24458
24562
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
24459
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24460
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24563
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24564
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24461
24565
  drawProjectedOrganicMouth(context, [
24462
24566
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24463
24567
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -24486,54 +24590,118 @@
24486
24590
  /**
24487
24591
  * Draws the soft floor shadow that anchors the single mesh in the frame.
24488
24592
  *
24593
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24594
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24595
+ *
24489
24596
  * @private helper of `octopus3d2AvatarVisual`
24490
24597
  */
24491
24598
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
24599
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
24600
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
24601
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
24602
+ const ry = size * 0.062;
24492
24603
  context.save();
24493
- context.fillStyle = `${palette.shadow}66`;
24494
- context.filter = `blur(${size * 0.024}px)`;
24604
+ context.translate(cx, cy);
24605
+ context.scale(1, ry / rx);
24606
+ const blurRadius = rx * 1.4;
24607
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
24608
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
24609
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
24610
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
24611
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
24612
+ context.fillStyle = shadowGradient;
24495
24613
  context.beginPath();
24496
- 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);
24614
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
24497
24615
  context.fill();
24498
24616
  context.restore();
24499
24617
  }
24618
+ /**
24619
+ * Number of latitude segments used by the single blobby octopus mesh.
24620
+ *
24621
+ * @private helper of `octopus3d2AvatarVisual`
24622
+ */
24623
+ const LATITUDE_PATCH_COUNT$2 = 12;
24624
+ /**
24625
+ * Number of longitude segments used by the single blobby octopus mesh.
24626
+ *
24627
+ * @private helper of `octopus3d2AvatarVisual`
24628
+ */
24629
+ const LONGITUDE_PATCH_COUNT$2 = 24;
24500
24630
  /**
24501
24631
  * Resolves all visible projected patches for the single blobby octopus mesh.
24502
24632
  *
24633
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
24634
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
24635
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
24636
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
24637
+ * corners are shared between neighboring patches.
24638
+ *
24503
24639
  * @private helper of `octopus3d2AvatarVisual`
24504
24640
  */
24505
24641
  function resolveVisibleBlobbyOctopusPatches(options) {
24506
24642
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
24507
- const latitudePatchCount = 12;
24508
- const longitudePatchCount = 24;
24643
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
24644
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
24509
24645
  const surfacePatches = [];
24646
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
24647
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
24648
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
24649
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
24650
+ }
24651
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
24652
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
24653
+ }
24654
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
24655
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
24656
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
24657
+ }
24658
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
24659
+ const transformedCornerSamples = new Array(cornerCount);
24660
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
24661
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
24662
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
24663
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
24664
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
24665
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
24666
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
24667
+ }
24668
+ }
24510
24669
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
24511
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
24512
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
24670
+ const startLatitude = latitudeBoundaries[latitudeIndex];
24671
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
24513
24672
  const centerLatitude = (startLatitude + endLatitude) / 2;
24514
24673
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
24674
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
24675
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
24515
24676
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
24516
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
24517
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
24677
+ const startLongitude = longitudeBoundaries[longitudeIndex];
24678
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
24518
24679
  const centerLongitude = (startLongitude + endLongitude) / 2;
24519
- const localCorners = [
24520
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
24521
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
24522
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
24523
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
24680
+ const transformedCorners = [
24681
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
24682
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
24683
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
24684
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
24524
24685
  ];
24525
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
24526
24686
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
24527
24687
  if (surfaceNormal.z <= 0.01) {
24528
24688
  continue;
24529
24689
  }
24530
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
24690
+ const projectedCorners = [
24691
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
24692
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
24693
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
24694
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
24695
+ ];
24531
24696
  surfacePatches.push({
24532
24697
  corners: projectedCorners,
24533
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
24534
- transformedCorners.length,
24535
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
24536
- fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
24698
+ averageDepth: (transformedCorners[0].z +
24699
+ transformedCorners[1].z +
24700
+ transformedCorners[2].z +
24701
+ transformedCorners[3].z) /
24702
+ 4,
24703
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
24704
+ fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
24537
24705
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
24538
24706
  });
24539
24707
  }
@@ -24549,12 +24717,21 @@
24549
24717
  * @private helper of `octopus3d2AvatarVisual`
24550
24718
  */
24551
24719
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
24720
+ const { morphologyProfile, animationPhase, timeMs } = options;
24721
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
24722
+ }
24723
+ /**
24724
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
24725
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
24726
+ *
24727
+ * @private helper of `octopus3d2AvatarVisual`
24728
+ */
24729
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
24552
24730
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
24553
24731
  const cosineLatitude = Math.max(0, Math.cos(latitude));
24554
24732
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
24555
24733
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
24556
24734
  const lowerBlend = Math.pow(verticalProgress, 1.42);
24557
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
24558
24735
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
24559
24736
  const horizontalScale = 1.02 +
24560
24737
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -24592,7 +24769,7 @@
24592
24769
  *
24593
24770
  * @private helper of `octopus3d2AvatarVisual`
24594
24771
  */
24595
- function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
24772
+ function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
24596
24773
  const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
24597
24774
  if (tonalProgress < 0.16) {
24598
24775
  return palette.highlight;
@@ -24632,6 +24809,1384 @@
24632
24809
  context.restore();
24633
24810
  }
24634
24811
 
24812
+ /* eslint-disable no-magic-numbers */
24813
+ /**
24814
+ * Light direction used by the continuous octopus mesh shading.
24815
+ *
24816
+ * @private helper of `octopus3d3AvatarVisual`
24817
+ */
24818
+ const LIGHT_DIRECTION$1 = normalizeVector3({
24819
+ x: 0.34,
24820
+ y: -0.62,
24821
+ z: 1,
24822
+ });
24823
+ /**
24824
+ * Real-octopus tentacle count used by the continuous lower mesh.
24825
+ *
24826
+ * @private helper of `octopus3d3AvatarVisual`
24827
+ */
24828
+ const OCTOPUS_TENTACLE_COUNT$1 = 8;
24829
+ /**
24830
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
24831
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
24832
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
24833
+ *
24834
+ * @private helper of `octopus3d3AvatarVisual`
24835
+ */
24836
+ const stableStateCache = new WeakMap();
24837
+ /**
24838
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
24839
+ * result on every subsequent call within the same `<Avatar/>` mount.
24840
+ *
24841
+ * @private helper of `octopus3d3AvatarVisual`
24842
+ */
24843
+ function getOctopus3d3StableState(createRandom) {
24844
+ const cached = stableStateCache.get(createRandom);
24845
+ if (cached !== undefined) {
24846
+ return cached;
24847
+ }
24848
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24849
+ const animationRandom = createRandom('octopus3d3-animation-profile');
24850
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
24851
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
24852
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
24853
+ const state = {
24854
+ morphologyProfile,
24855
+ animationPhase: animationRandom() * Math.PI * 2,
24856
+ leftEyePhaseOffset,
24857
+ rightEyePhaseOffset,
24858
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
24859
+ };
24860
+ stableStateCache.set(createRandom, state);
24861
+ return state;
24862
+ }
24863
+ /**
24864
+ * Octopus 3D 3 avatar visual.
24865
+ *
24866
+ * @private built-in avatar visual
24867
+ */
24868
+ const octopus3d3AvatarVisual = {
24869
+ id: 'octopus3d3',
24870
+ title: 'Octopus 3D 3',
24871
+ description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
24872
+ isAnimated: true,
24873
+ supportsPointerTracking: true,
24874
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
24875
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
24876
+ const sceneCenterX = size * 0.5;
24877
+ const sceneCenterY = size * 0.535;
24878
+ const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
24879
+ const meshCenter = {
24880
+ x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
24881
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
24882
+ z: interaction.intensity * size * 0.018,
24883
+ };
24884
+ const rotationY = -0.1 +
24885
+ Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
24886
+ interaction.bodyOffsetX * 0.22 +
24887
+ interaction.gazeX * 0.88;
24888
+ const rotationX = -0.07 +
24889
+ Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
24890
+ interaction.bodyOffsetY * 0.08 -
24891
+ interaction.gazeY * 0.38;
24892
+ const surfaceOptions = {
24893
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
24894
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
24895
+ radiusZ: size *
24896
+ morphologyProfile.body.bodyRadiusRatio *
24897
+ (1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
24898
+ morphologyProfile,
24899
+ timeMs,
24900
+ animationPhase,
24901
+ tentacleProfiles,
24902
+ };
24903
+ const surfacePatches = resolveVisibleContinuousOctopusPatches({
24904
+ ...surfaceOptions,
24905
+ center: meshCenter,
24906
+ rotationX,
24907
+ rotationY,
24908
+ sceneCenterX,
24909
+ sceneCenterY,
24910
+ size,
24911
+ palette,
24912
+ });
24913
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
24914
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
24915
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
24916
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
24917
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
24918
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
24919
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
24920
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
24921
+ drawAvatarFrame(context, size, palette);
24922
+ drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
24923
+ drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
24924
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
24925
+ drawContinuousSurfacePatch(context, surfacePatch);
24926
+ }
24927
+ drawProjectedSurfaceCurrents({
24928
+ context,
24929
+ surfaceOptions,
24930
+ center: meshCenter,
24931
+ rotationX,
24932
+ rotationY,
24933
+ sceneCenterX,
24934
+ sceneCenterY,
24935
+ size,
24936
+ palette,
24937
+ morphologyProfile,
24938
+ timeMs,
24939
+ animationPhase,
24940
+ });
24941
+ drawProjectedTentacleSuckers({
24942
+ context,
24943
+ surfaceOptions,
24944
+ center: meshCenter,
24945
+ rotationX,
24946
+ rotationY,
24947
+ sceneCenterX,
24948
+ sceneCenterY,
24949
+ size,
24950
+ palette,
24951
+ });
24952
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24953
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24954
+ drawProjectedOrganicMouth(context, [
24955
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24956
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
24957
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
24958
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
24959
+ },
24960
+ };
24961
+ /**
24962
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
24963
+ *
24964
+ * @private helper of `octopus3d3AvatarVisual`
24965
+ */
24966
+ function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
24967
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
24968
+ const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
24969
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
24970
+ return {
24971
+ centerLongitude: -Math.PI * 0.86 +
24972
+ progress * Math.PI * 1.72 +
24973
+ (tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
24974
+ widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
24975
+ lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
24976
+ swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
24977
+ depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
24978
+ phase: tentacleRandom() * Math.PI * 2,
24979
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
24980
+ };
24981
+ });
24982
+ }
24983
+ /**
24984
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
24985
+ *
24986
+ * @private helper of `octopus3d3AvatarVisual`
24987
+ */
24988
+ function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
24989
+ 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));
24990
+ glowGradient.addColorStop(0, `${palette.highlight}66`);
24991
+ glowGradient.addColorStop(0.34, `${palette.accent}2e`);
24992
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
24993
+ context.fillStyle = glowGradient;
24994
+ context.fillRect(0, 0, size, size);
24995
+ 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);
24996
+ lowerGradient.addColorStop(0, `${palette.secondary}25`);
24997
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
24998
+ context.fillStyle = lowerGradient;
24999
+ context.fillRect(0, 0, size, size);
25000
+ }
25001
+ /**
25002
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
25003
+ *
25004
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
25005
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
25006
+ *
25007
+ * @private helper of `octopus3d3AvatarVisual`
25008
+ */
25009
+ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
25010
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
25011
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
25012
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
25013
+ const ry = size * 0.06;
25014
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
25015
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
25016
+ context.save();
25017
+ context.translate(cx, cy);
25018
+ context.scale(1, ry / rx);
25019
+ const blurRadius = rx * 1.4;
25020
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
25021
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
25022
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
25023
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
25024
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
25025
+ context.fillStyle = shadowGradient;
25026
+ context.beginPath();
25027
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
25028
+ context.fill();
25029
+ context.restore();
25030
+ }
25031
+ /**
25032
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
25033
+ *
25034
+ * @private helper of `octopus3d3AvatarVisual`
25035
+ */
25036
+ const LATITUDE_PATCH_COUNT$1 = 16;
25037
+ /**
25038
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
25039
+ *
25040
+ * @private helper of `octopus3d3AvatarVisual`
25041
+ */
25042
+ const LONGITUDE_PATCH_COUNT$1 = 40;
25043
+ /**
25044
+ * Resolves visible projected patches for the continuous octopus mesh.
25045
+ *
25046
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
25047
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
25048
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
25049
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
25050
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
25051
+ *
25052
+ * @private helper of `octopus3d3AvatarVisual`
25053
+ */
25054
+ function resolveVisibleContinuousOctopusPatches(options) {
25055
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
25056
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
25057
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
25058
+ const surfacePatches = [];
25059
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
25060
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
25061
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
25062
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
25063
+ }
25064
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25065
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
25066
+ }
25067
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
25068
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
25069
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
25070
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25071
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25072
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25073
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
25074
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
25075
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
25076
+ }
25077
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25078
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
25079
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
25080
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
25081
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
25082
+ }
25083
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
25084
+ const transformedCornerSamples = new Array(cornerCount);
25085
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
25086
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
25087
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
25088
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
25089
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
25090
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
25091
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
25092
+ }
25093
+ }
25094
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
25095
+ const startLatitude = latitudeBoundaries[latitudeIndex];
25096
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
25097
+ const centerLatitude = (startLatitude + endLatitude) / 2;
25098
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
25099
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
25100
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
25101
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25102
+ const transformedCorners = [
25103
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
25104
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
25105
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
25106
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
25107
+ ];
25108
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
25109
+ if (surfaceNormal.z <= 0.008) {
25110
+ continue;
25111
+ }
25112
+ const projectedCorners = [
25113
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
25114
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
25115
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
25116
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
25117
+ ];
25118
+ surfacePatches.push({
25119
+ corners: projectedCorners,
25120
+ averageDepth: (transformedCorners[0].z +
25121
+ transformedCorners[1].z +
25122
+ transformedCorners[2].z +
25123
+ transformedCorners[3].z) /
25124
+ 4,
25125
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
25126
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
25127
+ outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
25128
+ });
25129
+ }
25130
+ }
25131
+ return surfacePatches;
25132
+ }
25133
+ /**
25134
+ * Samples one point on the continuous Octopus 3D 3 surface.
25135
+ *
25136
+ * The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
25137
+ * tentacled while still being rendered as one connected blobby mesh.
25138
+ *
25139
+ * @private helper of `octopus3d3AvatarVisual`
25140
+ */
25141
+ function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
25142
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
25143
+ }
25144
+ /**
25145
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
25146
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
25147
+ *
25148
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
25149
+ * corner grid, so the same longitude is reused across every latitude row and each
25150
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
25151
+ *
25152
+ * @private helper of `octopus3d3AvatarVisual`
25153
+ */
25154
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
25155
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
25156
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
25157
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
25158
+ const upperBlend = Math.pow(1 - verticalProgress, 1.28);
25159
+ const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
25160
+ const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
25161
+ const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
25162
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
25163
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
25164
+ animationPhase * 0.6 +
25165
+ timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
25166
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
25167
+ (0.3 + lowerBlend * 0.7);
25168
+ const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
25169
+ lowerBlend *
25170
+ tentacleInfluence.core *
25171
+ tentacleInfluence.swayScale;
25172
+ const horizontalScale = 1.04 +
25173
+ mantleRipple +
25174
+ lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
25175
+ lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
25176
+ upperBlend * 0.08;
25177
+ const depthScale = 1.06 +
25178
+ upperBlend * 0.16 +
25179
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
25180
+ lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
25181
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
25182
+ const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
25183
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
25184
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
25185
+ const lowerDrop = lowerBlend *
25186
+ radiusY *
25187
+ (0.18 +
25188
+ tentacleInfluence.core *
25189
+ (0.38 +
25190
+ tentacleInfluence.lengthScale * 0.22 +
25191
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
25192
+ return {
25193
+ x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
25194
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
25195
+ upperBlend * radiusY * 0.1 +
25196
+ lowerDrop +
25197
+ Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
25198
+ Math.cos(timeMs / 880 + tentacleInfluence.phase) *
25199
+ lowerBlend *
25200
+ tipBlend *
25201
+ tentacleInfluence.core *
25202
+ radiusY *
25203
+ 0.034,
25204
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
25205
+ Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
25206
+ lowerBlend *
25207
+ tentacleInfluence.core *
25208
+ radiusZ *
25209
+ 0.04,
25210
+ };
25211
+ }
25212
+ /**
25213
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
25214
+ *
25215
+ * @private helper of `octopus3d3AvatarVisual`
25216
+ */
25217
+ function resolveContinuousTentacleInfluence(options, longitude) {
25218
+ let totalWeight = 0;
25219
+ let weightedSin = 0;
25220
+ let weightedCos = 0;
25221
+ let weightedWidthScale = 0;
25222
+ let weightedLengthScale = 0;
25223
+ let weightedSwayScale = 0;
25224
+ let weightedDepthScale = 0;
25225
+ let weightedPhase = 0;
25226
+ for (const tentacleProfile of options.tentacleProfiles) {
25227
+ const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
25228
+ const width = 0.2 * tentacleProfile.widthScale;
25229
+ const weight = Math.exp(-(distance * distance) / (width * width));
25230
+ totalWeight += weight;
25231
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
25232
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
25233
+ weightedWidthScale += tentacleProfile.widthScale * weight;
25234
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
25235
+ weightedSwayScale += tentacleProfile.swayScale * weight;
25236
+ weightedDepthScale += tentacleProfile.depthScale * weight;
25237
+ weightedPhase += tentacleProfile.phase * weight;
25238
+ }
25239
+ if (totalWeight < 0.0001) {
25240
+ return {
25241
+ core: 0,
25242
+ centerLongitude: longitude,
25243
+ widthScale: 1,
25244
+ lengthScale: 1,
25245
+ swayScale: 1,
25246
+ depthScale: 1,
25247
+ phase: 0,
25248
+ };
25249
+ }
25250
+ return {
25251
+ core: clampNumber$1(totalWeight, 0, 1),
25252
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
25253
+ widthScale: weightedWidthScale / totalWeight,
25254
+ lengthScale: weightedLengthScale / totalWeight,
25255
+ swayScale: weightedSwayScale / totalWeight,
25256
+ depthScale: weightedDepthScale / totalWeight,
25257
+ phase: weightedPhase / totalWeight,
25258
+ };
25259
+ }
25260
+ /**
25261
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
25262
+ *
25263
+ * @private helper of `octopus3d3AvatarVisual`
25264
+ */
25265
+ function resolveContinuousLobeWave(options, longitude) {
25266
+ const { morphologyProfile, animationPhase, timeMs } = options;
25267
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
25268
+ animationPhase +
25269
+ timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
25270
+ 1) /
25271
+ 2);
25272
+ }
25273
+ /**
25274
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
25275
+ *
25276
+ * @private helper of `octopus3d3AvatarVisual`
25277
+ */
25278
+ function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
25279
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
25280
+ if (tonalProgress < 0.14) {
25281
+ return palette.highlight;
25282
+ }
25283
+ if (tonalProgress < 0.32) {
25284
+ return palette.secondary;
25285
+ }
25286
+ if (tonalProgress < 0.72) {
25287
+ return forwardness > 0.55 ? palette.secondary : palette.primary;
25288
+ }
25289
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
25290
+ }
25291
+ /**
25292
+ * Draws one projected mesh patch with soft shading and a subtle edge.
25293
+ *
25294
+ * @private helper of `octopus3d3AvatarVisual`
25295
+ */
25296
+ function drawContinuousSurfacePatch(context, surfacePatch) {
25297
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
25298
+ if (surfacePatch.lightIntensity > 0) {
25299
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
25300
+ }
25301
+ else if (surfacePatch.lightIntensity < 0) {
25302
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
25303
+ }
25304
+ context.save();
25305
+ context.beginPath();
25306
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
25307
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
25308
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
25309
+ }
25310
+ context.closePath();
25311
+ context.strokeStyle = surfacePatch.outlineColor;
25312
+ context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
25313
+ context.lineJoin = 'round';
25314
+ context.stroke();
25315
+ context.restore();
25316
+ }
25317
+ /**
25318
+ * Draws projected mantle-current lines on the front of the mesh.
25319
+ *
25320
+ * @private helper of `octopus3d3AvatarVisual`
25321
+ */
25322
+ function drawProjectedSurfaceCurrents(options) {
25323
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
25324
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
25325
+ const centerIndex = (currentCount - 1) / 2;
25326
+ context.save();
25327
+ context.lineCap = 'round';
25328
+ context.lineJoin = 'round';
25329
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
25330
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
25331
+ const projectedPoints = [];
25332
+ for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
25333
+ const progress = sampleIndex / 7;
25334
+ const latitude = -0.46 + progress * 0.74;
25335
+ const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
25336
+ const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
25337
+ if (scenePoint.z > center.z - size * 0.016) {
25338
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
25339
+ }
25340
+ }
25341
+ if (projectedPoints.length < 3) {
25342
+ continue;
25343
+ }
25344
+ context.beginPath();
25345
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
25346
+ for (const projectedPoint of projectedPoints.slice(1)) {
25347
+ context.lineTo(projectedPoint.x, projectedPoint.y);
25348
+ }
25349
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
25350
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
25351
+ context.stroke();
25352
+ }
25353
+ context.restore();
25354
+ }
25355
+ /**
25356
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
25357
+ *
25358
+ * @private helper of `octopus3d3AvatarVisual`
25359
+ */
25360
+ function drawProjectedTentacleSuckers(options) {
25361
+ const { surfaceOptions, size } = options;
25362
+ const { timeMs } = surfaceOptions;
25363
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
25364
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
25365
+ continue;
25366
+ }
25367
+ for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
25368
+ const latitude = 0.52 + suckerIndex * 0.14;
25369
+ const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
25370
+ const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
25371
+ drawProjectedSurfaceSpot({
25372
+ ...options,
25373
+ latitude,
25374
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
25375
+ radiusScale: size * (0.0065 - suckerIndex * 0.0007),
25376
+ });
25377
+ }
25378
+ }
25379
+ }
25380
+ /**
25381
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
25382
+ *
25383
+ * @private helper of `octopus3d3AvatarVisual`
25384
+ */
25385
+ function drawProjectedSurfaceSpot(options) {
25386
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
25387
+ const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
25388
+ const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
25389
+ const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
25390
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
25391
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
25392
+ return;
25393
+ }
25394
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
25395
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
25396
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
25397
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
25398
+ radiusScale *
25399
+ 0.74, size * 0.003, size * 0.018);
25400
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
25401
+ radiusScale *
25402
+ 0.52, size * 0.0024, size * 0.014);
25403
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
25404
+ context.save();
25405
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
25406
+ context.rotate(rotation);
25407
+ context.beginPath();
25408
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
25409
+ context.fillStyle = `${palette.highlight}73`;
25410
+ context.fill();
25411
+ context.strokeStyle = `${palette.highlight}99`;
25412
+ context.lineWidth = Math.max(0.7, size * 0.0028);
25413
+ context.stroke();
25414
+ context.restore();
25415
+ }
25416
+ /**
25417
+ * Resolves a signed angular distance from the source longitude to the target longitude.
25418
+ *
25419
+ * @private helper of `octopus3d3AvatarVisual`
25420
+ */
25421
+ function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
25422
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
25423
+ }
25424
+ /**
25425
+ * Smoothly maps a value between two bounds into `[0, 1]`.
25426
+ *
25427
+ * @private helper of `octopus3d3AvatarVisual`
25428
+ */
25429
+ function smoothStep$1(edgeStart, edgeEnd, value) {
25430
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
25431
+ return progress * progress * (3 - 2 * progress);
25432
+ }
25433
+
25434
+ /* eslint-disable no-magic-numbers */
25435
+ /**
25436
+ * Light direction used by the continuous Octopus 3D 4 mesh shading.
25437
+ *
25438
+ * @private helper of `octopus3d4AvatarVisual`
25439
+ */
25440
+ const LIGHT_DIRECTION = normalizeVector3({
25441
+ x: 0.32,
25442
+ y: -0.66,
25443
+ z: 1,
25444
+ });
25445
+ /**
25446
+ * Rim-light direction used to brighten the side of the silhouette.
25447
+ *
25448
+ * @private helper of `octopus3d4AvatarVisual`
25449
+ */
25450
+ const RIM_LIGHT_DIRECTION = normalizeVector3({
25451
+ x: -0.7,
25452
+ y: -0.18,
25453
+ z: 0.55,
25454
+ });
25455
+ /**
25456
+ * Real-octopus tentacle count used by the continuous lower mesh.
25457
+ *
25458
+ * @private helper of `octopus3d4AvatarVisual`
25459
+ */
25460
+ const OCTOPUS_TENTACLE_COUNT = 8;
25461
+ /**
25462
+ * Number of seeded skin spots painted across the octopus body.
25463
+ *
25464
+ * @private helper of `octopus3d4AvatarVisual`
25465
+ */
25466
+ const SKIN_SPOT_COUNT = 14;
25467
+ /**
25468
+ * Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
25469
+ *
25470
+ * @private helper of `octopus3d4AvatarVisual`
25471
+ */
25472
+ const octopus3d4StableStateCache = new WeakMap();
25473
+ /**
25474
+ * Returns the stable per-avatar state, computing it on first access and caching afterwards.
25475
+ *
25476
+ * @private helper of `octopus3d4AvatarVisual`
25477
+ */
25478
+ function getOctopus3d4StableState(createRandom) {
25479
+ const cached = octopus3d4StableStateCache.get(createRandom);
25480
+ if (cached !== undefined) {
25481
+ return cached;
25482
+ }
25483
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
25484
+ const animationRandom = createRandom('octopus3d4-animation-profile');
25485
+ const eyeRandom = createRandom('octopus3d4-eye-profile');
25486
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
25487
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
25488
+ const state = {
25489
+ morphologyProfile,
25490
+ animationPhase: animationRandom() * Math.PI * 2,
25491
+ leftEyePhaseOffset,
25492
+ rightEyePhaseOffset,
25493
+ tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
25494
+ skinSpots: createBlobbySkinSpots(createRandom),
25495
+ };
25496
+ octopus3d4StableStateCache.set(createRandom, state);
25497
+ return state;
25498
+ }
25499
+ /**
25500
+ * Octopus 3D 4 avatar visual.
25501
+ *
25502
+ * @private built-in avatar visual
25503
+ */
25504
+ const octopus3d4AvatarVisual = {
25505
+ id: 'octopus3d4',
25506
+ title: 'Octopus 3D 4',
25507
+ 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.',
25508
+ isAnimated: true,
25509
+ supportsPointerTracking: true,
25510
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
25511
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
25512
+ const sceneCenterX = size * 0.5;
25513
+ const sceneCenterY = size * 0.535;
25514
+ const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
25515
+ const meshCenter = {
25516
+ x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
25517
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
25518
+ z: interaction.intensity * size * 0.02,
25519
+ };
25520
+ const rotationY = -0.08 +
25521
+ Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
25522
+ interaction.bodyOffsetX * 0.24 +
25523
+ interaction.gazeX * 0.98;
25524
+ const rotationX = -0.07 +
25525
+ Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
25526
+ interaction.bodyOffsetY * 0.08 -
25527
+ interaction.gazeY * 0.42;
25528
+ const surfaceOptions = {
25529
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
25530
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
25531
+ radiusZ: size *
25532
+ morphologyProfile.body.bodyRadiusRatio *
25533
+ (1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
25534
+ morphologyProfile,
25535
+ timeMs,
25536
+ animationPhase,
25537
+ tentacleProfiles,
25538
+ };
25539
+ const surfacePatches = resolveVisibleBlobbyContinuousPatches({
25540
+ ...surfaceOptions,
25541
+ center: meshCenter,
25542
+ rotationX,
25543
+ rotationY,
25544
+ sceneCenterX,
25545
+ sceneCenterY,
25546
+ size,
25547
+ palette,
25548
+ });
25549
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
25550
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
25551
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
25552
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
25553
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
25554
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
25555
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
25556
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
25557
+ drawAvatarFrame(context, size, palette);
25558
+ drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
25559
+ drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
25560
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
25561
+ drawBlobbyContinuousSurfacePatch(context, surfacePatch);
25562
+ }
25563
+ drawBlobbySkinSpots({
25564
+ context,
25565
+ surfaceOptions,
25566
+ center: meshCenter,
25567
+ rotationX,
25568
+ rotationY,
25569
+ sceneCenterX,
25570
+ sceneCenterY,
25571
+ size,
25572
+ palette,
25573
+ skinSpots,
25574
+ });
25575
+ drawBlobbyContinuousCurrents({
25576
+ context,
25577
+ surfaceOptions,
25578
+ center: meshCenter,
25579
+ rotationX,
25580
+ rotationY,
25581
+ sceneCenterX,
25582
+ sceneCenterY,
25583
+ size,
25584
+ palette,
25585
+ morphologyProfile,
25586
+ timeMs,
25587
+ animationPhase,
25588
+ });
25589
+ drawBlobbyContinuousSuckers({
25590
+ context,
25591
+ surfaceOptions,
25592
+ center: meshCenter,
25593
+ rotationX,
25594
+ rotationY,
25595
+ sceneCenterX,
25596
+ sceneCenterY,
25597
+ size,
25598
+ palette,
25599
+ });
25600
+ drawBlobbyContinuousGloss({
25601
+ context,
25602
+ surfaceOptions,
25603
+ center: meshCenter,
25604
+ rotationX,
25605
+ rotationY,
25606
+ sceneCenterX,
25607
+ sceneCenterY,
25608
+ size,
25609
+ palette,
25610
+ });
25611
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
25612
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
25613
+ drawProjectedOrganicMouth(context, [
25614
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
25615
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
25616
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
25617
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
25618
+ },
25619
+ };
25620
+ /**
25621
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
25622
+ *
25623
+ * @private helper of `octopus3d4AvatarVisual`
25624
+ */
25625
+ function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
25626
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
25627
+ const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
25628
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
25629
+ return {
25630
+ centerLongitude: -Math.PI * 0.9 +
25631
+ progress * Math.PI * 1.8 +
25632
+ (tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
25633
+ widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
25634
+ lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
25635
+ swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
25636
+ depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
25637
+ curlScale: 0.62 + tentacleRandom() * 0.46,
25638
+ primaryPhase: tentacleRandom() * Math.PI * 2,
25639
+ secondaryPhase: tentacleRandom() * Math.PI * 2,
25640
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
25641
+ };
25642
+ });
25643
+ }
25644
+ /**
25645
+ * Creates seeded skin pigment spots distributed across the upper octopus mesh.
25646
+ *
25647
+ * @private helper of `octopus3d4AvatarVisual`
25648
+ */
25649
+ function createBlobbySkinSpots(createRandom) {
25650
+ const spotRandom = createRandom('octopus3d4-skin-spots');
25651
+ return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
25652
+ latitude: -0.45 + spotRandom() * 0.7,
25653
+ longitude: -0.5 + spotRandom() * 1.0,
25654
+ radiusScale: 0.0028 + spotRandom() * 0.0052,
25655
+ opacity: 0.16 + spotRandom() * 0.22,
25656
+ }));
25657
+ }
25658
+ /**
25659
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
25660
+ *
25661
+ * @private helper of `octopus3d4AvatarVisual`
25662
+ */
25663
+ function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
25664
+ 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));
25665
+ glowGradient.addColorStop(0, `${palette.highlight}74`);
25666
+ glowGradient.addColorStop(0.32, `${palette.accent}32`);
25667
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
25668
+ context.fillStyle = glowGradient;
25669
+ context.fillRect(0, 0, size, size);
25670
+ 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);
25671
+ lowerGradient.addColorStop(0, `${palette.secondary}28`);
25672
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
25673
+ context.fillStyle = lowerGradient;
25674
+ context.fillRect(0, 0, size, size);
25675
+ }
25676
+ /**
25677
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
25678
+ *
25679
+ * @private helper of `octopus3d4AvatarVisual`
25680
+ */
25681
+ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
25682
+ const cx = size * 0.5 + interaction.gazeX * size * 0.046;
25683
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
25684
+ const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
25685
+ const ry = size * 0.062;
25686
+ context.save();
25687
+ context.translate(cx, cy);
25688
+ context.scale(1, ry / rx);
25689
+ const blurRadius = rx * 1.42;
25690
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
25691
+ shadowGradient.addColorStop(0, `${palette.shadow}82`);
25692
+ shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
25693
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
25694
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
25695
+ context.fillStyle = shadowGradient;
25696
+ context.beginPath();
25697
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
25698
+ context.fill();
25699
+ context.restore();
25700
+ }
25701
+ /**
25702
+ * Number of latitude segments used by the continuous Octopus 3D 4 mesh.
25703
+ *
25704
+ * @private helper of `octopus3d4AvatarVisual`
25705
+ */
25706
+ const LATITUDE_PATCH_COUNT = 20;
25707
+ /**
25708
+ * Number of longitude segments used by the continuous Octopus 3D 4 mesh.
25709
+ *
25710
+ * @private helper of `octopus3d4AvatarVisual`
25711
+ */
25712
+ const LONGITUDE_PATCH_COUNT = 48;
25713
+ /**
25714
+ * Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
25715
+ *
25716
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
25717
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
25718
+ * than re-evaluated for every patch corner.
25719
+ *
25720
+ * @private helper of `octopus3d4AvatarVisual`
25721
+ */
25722
+ function resolveVisibleBlobbyContinuousPatches(options) {
25723
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
25724
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
25725
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
25726
+ const surfacePatches = [];
25727
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
25728
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
25729
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
25730
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
25731
+ }
25732
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25733
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
25734
+ }
25735
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
25736
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
25737
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
25738
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25739
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25740
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25741
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
25742
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
25743
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
25744
+ }
25745
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25746
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
25747
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
25748
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
25749
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
25750
+ }
25751
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
25752
+ const transformedCornerSamples = new Array(cornerCount);
25753
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
25754
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
25755
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
25756
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
25757
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
25758
+ const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
25759
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
25760
+ }
25761
+ }
25762
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
25763
+ const startLatitude = latitudeBoundaries[latitudeIndex];
25764
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
25765
+ const centerLatitude = (startLatitude + endLatitude) / 2;
25766
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
25767
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
25768
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
25769
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25770
+ const transformedCorners = [
25771
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
25772
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
25773
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
25774
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
25775
+ ];
25776
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
25777
+ if (surfaceNormal.z <= 0.006) {
25778
+ continue;
25779
+ }
25780
+ const projectedCorners = [
25781
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
25782
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
25783
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
25784
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
25785
+ ];
25786
+ const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
25787
+ const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
25788
+ surfacePatches.push({
25789
+ corners: projectedCorners,
25790
+ averageDepth: (transformedCorners[0].z +
25791
+ transformedCorners[1].z +
25792
+ transformedCorners[2].z +
25793
+ transformedCorners[3].z) /
25794
+ 4,
25795
+ lightIntensity,
25796
+ rimLightIntensity,
25797
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
25798
+ outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
25799
+ });
25800
+ }
25801
+ }
25802
+ return surfacePatches;
25803
+ }
25804
+ /**
25805
+ * Samples one point on the continuous Octopus 3D 4 surface.
25806
+ *
25807
+ * @private helper of `octopus3d4AvatarVisual`
25808
+ */
25809
+ function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
25810
+ return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
25811
+ }
25812
+ /**
25813
+ * Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
25814
+ *
25815
+ * The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
25816
+ * across every latitude row and each tentacle/lobe value is computed once per frame instead
25817
+ * of `latitudePatchCount * 4` times.
25818
+ *
25819
+ * @private helper of `octopus3d4AvatarVisual`
25820
+ */
25821
+ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
25822
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
25823
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
25824
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
25825
+ const upperBlend = Math.pow(1 - verticalProgress, 1.32);
25826
+ const lowerBlend = smoothStep(0.34, 1, verticalProgress);
25827
+ const tipBlend = smoothStep(0.66, 1, verticalProgress);
25828
+ const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
25829
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
25830
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
25831
+ animationPhase * 0.6 +
25832
+ timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
25833
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
25834
+ (0.32 + lowerBlend * 0.7);
25835
+ const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
25836
+ lowerBlend *
25837
+ tentacleInfluence.core *
25838
+ tentacleInfluence.swayScale;
25839
+ const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
25840
+ lowerBlend *
25841
+ tentacleInfluence.core *
25842
+ tentacleInfluence.swayScale *
25843
+ 0.42;
25844
+ const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
25845
+ tipBlend *
25846
+ tentacleInfluence.core *
25847
+ tentacleInfluence.curlScale *
25848
+ 0.36;
25849
+ const horizontalScale = 1.06 +
25850
+ mantleRipple +
25851
+ lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
25852
+ lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
25853
+ upperBlend * 0.08;
25854
+ const depthScale = 1.08 +
25855
+ upperBlend * 0.16 +
25856
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
25857
+ lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
25858
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
25859
+ const tentacleTubeRadius = lowerBlend *
25860
+ tentacleInfluence.core *
25861
+ (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
25862
+ radiusX;
25863
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
25864
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
25865
+ const lowerDrop = lowerBlend *
25866
+ radiusY *
25867
+ (0.2 +
25868
+ tentacleInfluence.core *
25869
+ (0.42 +
25870
+ tentacleInfluence.lengthScale * 0.24 +
25871
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
25872
+ const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
25873
+ return {
25874
+ x: Math.sin(effectiveLongitude) * planarRadiusX +
25875
+ combinedTentacleSway +
25876
+ tentacleCurl * radiusX * 0.18,
25877
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
25878
+ upperBlend * radiusY * 0.12 +
25879
+ lowerDrop +
25880
+ Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
25881
+ Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
25882
+ lowerBlend *
25883
+ tipBlend *
25884
+ tentacleInfluence.core *
25885
+ radiusY *
25886
+ 0.04,
25887
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
25888
+ Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
25889
+ lowerBlend *
25890
+ tentacleInfluence.core *
25891
+ radiusZ *
25892
+ 0.044 +
25893
+ tentacleCurl * radiusZ * 0.14,
25894
+ };
25895
+ }
25896
+ /**
25897
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
25898
+ *
25899
+ * @private helper of `octopus3d4AvatarVisual`
25900
+ */
25901
+ function resolveBlobbyTentacleInfluence(options, longitude) {
25902
+ let totalWeight = 0;
25903
+ let weightedSin = 0;
25904
+ let weightedCos = 0;
25905
+ let weightedWidthScale = 0;
25906
+ let weightedLengthScale = 0;
25907
+ let weightedSwayScale = 0;
25908
+ let weightedDepthScale = 0;
25909
+ let weightedCurlScale = 0;
25910
+ let weightedPrimaryPhase = 0;
25911
+ let weightedSecondaryPhase = 0;
25912
+ for (const tentacleProfile of options.tentacleProfiles) {
25913
+ const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
25914
+ const width = 0.22 * tentacleProfile.widthScale;
25915
+ const weight = Math.exp(-(distance * distance) / (width * width));
25916
+ totalWeight += weight;
25917
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
25918
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
25919
+ weightedWidthScale += tentacleProfile.widthScale * weight;
25920
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
25921
+ weightedSwayScale += tentacleProfile.swayScale * weight;
25922
+ weightedDepthScale += tentacleProfile.depthScale * weight;
25923
+ weightedCurlScale += tentacleProfile.curlScale * weight;
25924
+ weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
25925
+ weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
25926
+ }
25927
+ if (totalWeight < 0.0001) {
25928
+ return {
25929
+ core: 0,
25930
+ centerLongitude: longitude,
25931
+ widthScale: 1,
25932
+ lengthScale: 1,
25933
+ swayScale: 1,
25934
+ depthScale: 1,
25935
+ curlScale: 1,
25936
+ primaryPhase: 0,
25937
+ secondaryPhase: 0,
25938
+ };
25939
+ }
25940
+ return {
25941
+ core: clampNumber$1(totalWeight, 0, 1),
25942
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
25943
+ widthScale: weightedWidthScale / totalWeight,
25944
+ lengthScale: weightedLengthScale / totalWeight,
25945
+ swayScale: weightedSwayScale / totalWeight,
25946
+ depthScale: weightedDepthScale / totalWeight,
25947
+ curlScale: weightedCurlScale / totalWeight,
25948
+ primaryPhase: weightedPrimaryPhase / totalWeight,
25949
+ secondaryPhase: weightedSecondaryPhase / totalWeight,
25950
+ };
25951
+ }
25952
+ /**
25953
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
25954
+ *
25955
+ * @private helper of `octopus3d4AvatarVisual`
25956
+ */
25957
+ function resolveBlobbyLobeWave(options, longitude) {
25958
+ const { morphologyProfile, animationPhase, timeMs } = options;
25959
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
25960
+ animationPhase +
25961
+ timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
25962
+ 1) /
25963
+ 2);
25964
+ }
25965
+ /**
25966
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
25967
+ *
25968
+ * @private helper of `octopus3d4AvatarVisual`
25969
+ */
25970
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
25971
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
25972
+ if (tonalProgress < 0.12) {
25973
+ return palette.highlight;
25974
+ }
25975
+ if (tonalProgress < 0.3) {
25976
+ return palette.secondary;
25977
+ }
25978
+ if (tonalProgress < 0.72) {
25979
+ return forwardness > 0.56 ? palette.secondary : palette.primary;
25980
+ }
25981
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
25982
+ }
25983
+ /**
25984
+ * Draws one projected mesh patch with multi-pass shading and a soft edge.
25985
+ *
25986
+ * @private helper of `octopus3d4AvatarVisual`
25987
+ */
25988
+ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
25989
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
25990
+ if (surfacePatch.lightIntensity > 0) {
25991
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
25992
+ }
25993
+ else if (surfacePatch.lightIntensity < 0) {
25994
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
25995
+ }
25996
+ if (surfacePatch.rimLightIntensity > 0.04) {
25997
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
25998
+ }
25999
+ context.save();
26000
+ context.beginPath();
26001
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
26002
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
26003
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
26004
+ }
26005
+ context.closePath();
26006
+ context.strokeStyle = surfacePatch.outlineColor;
26007
+ context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
26008
+ context.lineJoin = 'round';
26009
+ context.stroke();
26010
+ context.restore();
26011
+ }
26012
+ /**
26013
+ * Draws projected mantle-current lines on the front of the mesh.
26014
+ *
26015
+ * @private helper of `octopus3d4AvatarVisual`
26016
+ */
26017
+ function drawBlobbyContinuousCurrents(options) {
26018
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
26019
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
26020
+ const centerIndex = (currentCount - 1) / 2;
26021
+ context.save();
26022
+ context.lineCap = 'round';
26023
+ context.lineJoin = 'round';
26024
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
26025
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
26026
+ const projectedPoints = [];
26027
+ for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
26028
+ const progress = sampleIndex / 8;
26029
+ const latitude = -0.48 + progress * 0.78;
26030
+ const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
26031
+ const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
26032
+ if (scenePoint.z > center.z - size * 0.016) {
26033
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
26034
+ }
26035
+ }
26036
+ if (projectedPoints.length < 3) {
26037
+ continue;
26038
+ }
26039
+ context.beginPath();
26040
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
26041
+ for (const projectedPoint of projectedPoints.slice(1)) {
26042
+ context.lineTo(projectedPoint.x, projectedPoint.y);
26043
+ }
26044
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
26045
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
26046
+ context.stroke();
26047
+ }
26048
+ context.restore();
26049
+ }
26050
+ /**
26051
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
26052
+ *
26053
+ * @private helper of `octopus3d4AvatarVisual`
26054
+ */
26055
+ function drawBlobbyContinuousSuckers(options) {
26056
+ const { surfaceOptions, size } = options;
26057
+ const { timeMs } = surfaceOptions;
26058
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
26059
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
26060
+ continue;
26061
+ }
26062
+ for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
26063
+ const latitude = 0.5 + suckerIndex * 0.12;
26064
+ const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
26065
+ const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
26066
+ drawBlobbyContinuousSurfaceSpot({
26067
+ ...options,
26068
+ latitude,
26069
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
26070
+ radiusScale: size * (0.0068 - suckerIndex * 0.0006),
26071
+ });
26072
+ }
26073
+ }
26074
+ }
26075
+ /**
26076
+ * Draws seeded pigment spots across the upper mesh for a richer skin texture.
26077
+ *
26078
+ * @private helper of `octopus3d4AvatarVisual`
26079
+ */
26080
+ function drawBlobbySkinSpots(options) {
26081
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
26082
+ for (const skinSpot of skinSpots) {
26083
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
26084
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
26085
+ if (sceneCenterPoint.z <= center.z - size * 0.01) {
26086
+ continue;
26087
+ }
26088
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
26089
+ const spotRadius = size * skinSpot.radiusScale;
26090
+ context.save();
26091
+ context.beginPath();
26092
+ context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
26093
+ context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
26094
+ context.fill();
26095
+ context.restore();
26096
+ }
26097
+ }
26098
+ /**
26099
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
26100
+ *
26101
+ * @private helper of `octopus3d4AvatarVisual`
26102
+ */
26103
+ function drawBlobbyContinuousSurfaceSpot(options) {
26104
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
26105
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
26106
+ const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
26107
+ const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
26108
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
26109
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
26110
+ return;
26111
+ }
26112
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
26113
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
26114
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
26115
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
26116
+ radiusScale *
26117
+ 0.78, size * 0.003, size * 0.02);
26118
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
26119
+ radiusScale *
26120
+ 0.54, size * 0.0024, size * 0.015);
26121
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
26122
+ context.save();
26123
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
26124
+ context.rotate(rotation);
26125
+ context.beginPath();
26126
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
26127
+ context.fillStyle = `${palette.highlight}80`;
26128
+ context.fill();
26129
+ context.strokeStyle = `${palette.highlight}a8`;
26130
+ context.lineWidth = Math.max(0.7, size * 0.0028);
26131
+ context.stroke();
26132
+ context.restore();
26133
+ }
26134
+ /**
26135
+ * Draws a soft, slowly drifting gloss highlight on the front of the mesh.
26136
+ *
26137
+ * @private helper of `octopus3d4AvatarVisual`
26138
+ */
26139
+ function drawBlobbyContinuousGloss(options) {
26140
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
26141
+ const { timeMs, animationPhase } = surfaceOptions;
26142
+ const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
26143
+ const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
26144
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
26145
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
26146
+ if (sceneCenterPoint.z <= center.z) {
26147
+ return;
26148
+ }
26149
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
26150
+ const glossRadius = size * 0.058;
26151
+ context.save();
26152
+ const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
26153
+ glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
26154
+ glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
26155
+ glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
26156
+ context.fillStyle = glossGradient;
26157
+ context.beginPath();
26158
+ context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
26159
+ context.fill();
26160
+ context.restore();
26161
+ }
26162
+ /**
26163
+ * Resolves a signed angular distance from the source longitude to the target longitude.
26164
+ *
26165
+ * @private helper of `octopus3d4AvatarVisual`
26166
+ */
26167
+ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
26168
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
26169
+ }
26170
+ /**
26171
+ * Smoothly maps a value between two bounds into `[0, 1]`.
26172
+ *
26173
+ * @private helper of `octopus3d4AvatarVisual`
26174
+ */
26175
+ function smoothStep(edgeStart, edgeEnd, value) {
26176
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
26177
+ return progress * progress * (3 - 2 * progress);
26178
+ }
26179
+ /**
26180
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
26181
+ *
26182
+ * @private helper of `octopus3d4AvatarVisual`
26183
+ */
26184
+ function formatAlphaHex(opacity) {
26185
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
26186
+ .toString(16)
26187
+ .padStart(2, '0');
26188
+ }
26189
+
24635
26190
  /* eslint-disable no-magic-numbers */
24636
26191
  /**
24637
26192
  * Octopus avatar visual.
@@ -25402,6 +26957,8 @@
25402
26957
  octopus3AvatarVisual,
25403
26958
  octopus3dAvatarVisual,
25404
26959
  octopus3d2AvatarVisual,
26960
+ octopus3d3AvatarVisual,
26961
+ octopus3d4AvatarVisual,
25405
26962
  asciiOctopusAvatarVisual,
25406
26963
  minecraftAvatarVisual,
25407
26964
  minecraft2AvatarVisual,
@@ -27690,7 +29247,15 @@
27690
29247
  * Builds the teammate request text, optionally including context.
27691
29248
  */
27692
29249
  function buildTeammateRequest(message, context) {
27693
- return context ? `${message}\n\nContext:\n${context}` : message;
29250
+ if (!context) {
29251
+ return message;
29252
+ }
29253
+ return _spaceTrim.spaceTrim((block) => `
29254
+ ${block(message)}
29255
+
29256
+ Context:
29257
+ ${block(context)}
29258
+ `);
27694
29259
  }
27695
29260
  /**
27696
29261
  * Builds a minimal chat prompt for teammate calls.
@@ -31516,7 +33081,11 @@
31516
33081
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
31517
33082
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
31518
33083
  const contentToReturn = wasCharacterTruncated
31519
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
33084
+ ? _spaceTrim.spaceTrim((block) => `
33085
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
33086
+
33087
+ [...truncated...]
33088
+ `)
31520
33089
  : lineRangedContent;
31521
33090
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
31522
33091
  if (wasCharacterTruncated) {
@@ -35005,11 +36574,19 @@
35005
36574
  try {
35006
36575
  const json = JSON.parse(content);
35007
36576
  const formattedJson = JSON.stringify(json, null, 4);
35008
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
36577
+ return _spaceTrim.spaceTrim((block) => `
36578
+ \`\`\`json
36579
+ ${block(formattedJson)}
36580
+ \`\`\`
36581
+ `);
35009
36582
  }
35010
36583
  catch (error) {
35011
36584
  // If JSON is invalid, still import it but maybe not as pretty JSON
35012
- return `\`\`\`json\n${content}\n\`\`\``;
36585
+ return _spaceTrim.spaceTrim((block) => `
36586
+ \`\`\`json
36587
+ ${block(content)}
36588
+ \`\`\`
36589
+ `);
35013
36590
  }
35014
36591
  },
35015
36592
  };
@@ -35033,7 +36610,11 @@
35033
36610
  import(content, mimeType) {
35034
36611
  const extension = mimeTypeToExtension(mimeType);
35035
36612
  const codeBlockType = extension || 'txt';
35036
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
36613
+ return _spaceTrim.spaceTrim((block) => `
36614
+ \`\`\`${codeBlockType}
36615
+ ${block(content)}
36616
+ \`\`\`
36617
+ `);
35037
36618
  },
35038
36619
  };
35039
36620
 
@@ -35280,11 +36861,19 @@
35280
36861
  const examples = [];
35281
36862
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
35282
36863
  if (initialMessage) {
35283
- examples.push(`**Agent:**\n${initialMessage}`);
36864
+ examples.push(_spaceTrim.spaceTrim((block) => `
36865
+ **Agent:**
36866
+ ${block(initialMessage)}
36867
+ `));
35284
36868
  }
35285
36869
  if (samples && samples.length > 0) {
35286
36870
  for (const sample of samples) {
35287
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
36871
+ examples.push(_spaceTrim.spaceTrim((block) => `
36872
+ **User:** ${block(String(sample.question))}
36873
+
36874
+ **Agent:**
36875
+ ${block(sample.answer)}
36876
+ `));
35288
36877
  }
35289
36878
  }
35290
36879
  return examples;
@@ -39809,7 +41398,12 @@
39809
41398
  * @private internal function of `$registeredLlmToolsMessage`
39810
41399
  */
39811
41400
  function createUsedEnvMessage() {
39812
- return $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
41401
+ return $usedEnvFilename === null
41402
+ ? `Unknown \`.env\` file`
41403
+ : _spaceTrim.spaceTrim(`
41404
+ Used \`.env\` file:
41405
+ ${$usedEnvFilename}
41406
+ `);
39813
41407
  }
39814
41408
  // TODO: [®] DRY Register logic
39815
41409
  // TODO: [🧠][⚛] Maybe pass env as argument
@@ -40349,7 +41943,7 @@
40349
41943
  *
40350
41944
  * It looks for environment variables:
40351
41945
  * - `process.env.OPENAI_API_KEY`
40352
- * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
41946
+ * - `process.env.ANTHROPIC_API_KEY` (or the deprecated `process.env.ANTHROPIC_CLAUDE_API_KEY`)
40353
41947
  * - ...
40354
41948
  *
40355
41949
  * @see Environment variables documentation or .env file for required variables.
@@ -40449,7 +42043,7 @@
40449
42043
  *
40450
42044
  * It looks for environment variables:
40451
42045
  * - `process.env.OPENAI_API_KEY`
40452
- * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
42046
+ * - `process.env.ANTHROPIC_API_KEY` (or the deprecated `process.env.ANTHROPIC_CLAUDE_API_KEY`)
40453
42047
  * - ...
40454
42048
  *
40455
42049
  * @param options Configuration options for the LLM tools
@@ -48879,7 +50473,9 @@
48879
50473
  if (!executionTools || !executionTools.script) {
48880
50474
  throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
48881
50475
  }
48882
- return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
50476
+ return Array.isArray(executionTools.script)
50477
+ ? executionTools.script
50478
+ : [executionTools.script];
48883
50479
  }
48884
50480
  /**
48885
50481
  * Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
@@ -49301,7 +50897,7 @@
49301
50897
  * Computes one stable hash from a JSON-serializable value.
49302
50898
  */
49303
50899
  function computeJsonHash$1(value) {
49304
- return cryptoJs.SHA256(JSON.stringify(value)).toString();
50900
+ return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
49305
50901
  }
49306
50902
  /**
49307
50903
  * Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
@@ -49459,7 +51055,7 @@
49459
51055
  * Computes one stable hash from a JSON-serializable value.
49460
51056
  */
49461
51057
  function computeJsonHash(value) {
49462
- return cryptoJs.SHA256(JSON.stringify(value)).toString();
51058
+ return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
49463
51059
  }
49464
51060
  /**
49465
51061
  * Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
@@ -49795,7 +51391,11 @@
49795
51391
  };
49796
51392
  }
49797
51393
  return {
49798
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
51394
+ content: _spaceTrim.spaceTrim((block) => `
51395
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
51396
+
51397
+ [...truncated...]
51398
+ `),
49799
51399
  isTruncated: true,
49800
51400
  };
49801
51401
  }
@@ -50261,7 +51861,7 @@
50261
51861
  * Returns a virtual model name representing the agent behavior.
50262
51862
  */
50263
51863
  get modelName() {
50264
- const hash = cryptoJs.SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
51864
+ const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
50265
51865
  const agentId = hash.substring(0, 10);
50266
51866
  return (normalizeToKebabCase(this.title) + '-' + agentId);
50267
51867
  }