@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/esm/index.es.js CHANGED
@@ -7,20 +7,20 @@ import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
7
7
  import { mkdir, rm, readFile, readdir, rename, rmdir, stat, access, constants, writeFile, watch, unlink } from 'fs/promises';
8
8
  import { spawn } from 'child_process';
9
9
  import { forTime } from 'waitasecond';
10
- import { SHA256 } from 'crypto-js';
10
+ import CryptoJS from 'crypto-js';
11
11
  import hexEncoder from 'crypto-js/enc-hex';
12
12
  import { basename, join, dirname, isAbsolute, relative } from 'path';
13
13
  import { Readability } from '@mozilla/readability';
14
14
  import { JSDOM } from 'jsdom';
15
- import { Converter } from 'showdown';
15
+ import showdown from 'showdown';
16
16
  import * as dotenv from 'dotenv';
17
17
  import sha256 from 'crypto-js/sha256';
18
18
  import JSZip from 'jszip';
19
19
  import { Subject, BehaviorSubject } from 'rxjs';
20
- import { randomBytes } from 'crypto';
20
+ import 'crypto';
21
21
  import moment from 'moment';
22
22
  import { lookup, extension } from 'mime-types';
23
- import { parse, unparse } from 'papaparse';
23
+ import papaparse from 'papaparse';
24
24
  import { fileSearchTool, tool, Agent as Agent$1, webSearchTool, run, setDefaultOpenAIClient, setDefaultOpenAIKey } from '@openai/agents';
25
25
  import OpenAI from 'openai';
26
26
 
@@ -38,7 +38,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
38
38
  * @generated
39
39
  * @see https://github.com/webgptorg/promptbook
40
40
  */
41
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-98';
41
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0';
42
42
  /**
43
43
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
44
44
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2093,7 +2093,7 @@ const _AnthropicClaudeMetadataRegistration = $llmToolsMetadataRegister.register(
2093
2093
  title: 'Anthropic Claude',
2094
2094
  packageName: '@promptbook/anthropic-claude',
2095
2095
  className: 'AnthropicClaudeExecutionTools',
2096
- envVariables: ['ANTHROPIC_CLAUDE_API_KEY'],
2096
+ envVariables: ['ANTHROPIC_API_KEY', 'ANTHROPIC_CLAUDE_API_KEY'],
2097
2097
  trustLevel: 'CLOSED',
2098
2098
  order: MODEL_ORDERS.TOP_TIER,
2099
2099
  getBoilerplateConfiguration() {
@@ -2110,14 +2110,17 @@ const _AnthropicClaudeMetadataRegistration = $llmToolsMetadataRegister.register(
2110
2110
  };
2111
2111
  },
2112
2112
  createConfigurationFromEnv(env) {
2113
+ var _a;
2113
2114
  // Note: Note using `process.env` BUT `env` to pass in the environment variables dynamically
2114
- if (typeof env.ANTHROPIC_CLAUDE_API_KEY === 'string') {
2115
+ // Note: `ANTHROPIC_API_KEY` takes precedence over the deprecated `ANTHROPIC_CLAUDE_API_KEY`
2116
+ const apiKey = (_a = env.ANTHROPIC_API_KEY) !== null && _a !== void 0 ? _a : env.ANTHROPIC_CLAUDE_API_KEY;
2117
+ if (typeof apiKey === 'string') {
2115
2118
  return {
2116
2119
  title: 'Claude (from env)',
2117
2120
  packageName: '@promptbook/anthropic-claude',
2118
2121
  className: 'AnthropicClaudeExecutionTools',
2119
2122
  options: {
2120
- apiKey: env.ANTHROPIC_CLAUDE_API_KEY,
2123
+ apiKey,
2121
2124
  },
2122
2125
  };
2123
2126
  }
@@ -2346,7 +2349,7 @@ class ParseError extends Error {
2346
2349
  * @private internal helper function
2347
2350
  */
2348
2351
  function $randomToken(randomness) {
2349
- return randomBytes(randomness).toString('hex');
2352
+ return CryptoJS.lib.WordArray.random(randomness).toString(CryptoJS.enc.Hex);
2350
2353
  }
2351
2354
  // TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
2352
2355
  // TODO: Maybe use nanoid instead https://github.com/ai/nanoid
@@ -10020,7 +10023,7 @@ class OpenAiVectorStoreKnowledgeSourcePreparer {
10020
10023
  return { skippedReason: 'invalid_data_url' };
10021
10024
  }
10022
10025
  return {
10023
- file: new File([parsed.buffer], parsed.filename, {
10026
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
10024
10027
  type: parsed.mimeType,
10025
10028
  }),
10026
10029
  sizeBytes: parsed.buffer.length,
@@ -13301,7 +13304,7 @@ function $registeredScrapersMessage(availableScrapers) {
13301
13304
  * @public exported from `@promptbook/editable`
13302
13305
  */
13303
13306
  function knowledgeSourceContentToName(knowledgeSourceContent) {
13304
- const hash = SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
13307
+ const hash = CryptoJS.SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
13305
13308
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
13306
13309
  .toString( /* hex */)
13307
13310
  .substring(0, 20);
@@ -14181,7 +14184,7 @@ function csvParse(value /* <- TODO: string_csv */, settings, schema /* <- TODO:
14181
14184
  console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
14182
14185
  value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14183
14186
  }
14184
- const csv = parse(value, settings);
14187
+ const csv = papaparse.parse(value, settings);
14185
14188
  return csv;
14186
14189
  }
14187
14190
 
@@ -14266,10 +14269,10 @@ const CsvFormatParser = {
14266
14269
  i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
14267
14270
  );
14268
14271
  */
14269
- await onProgress(unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
14272
+ await onProgress(papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
14270
14273
  }
14271
14274
  }
14272
- return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14275
+ return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14273
14276
  },
14274
14277
  },
14275
14278
  {
@@ -14297,7 +14300,7 @@ const CsvFormatParser = {
14297
14300
  return /* not await */ mapCallback({ [key]: value }, index, array.length);
14298
14301
  }));
14299
14302
  }));
14300
- return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14303
+ return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
14301
14304
  },
14302
14305
  },
14303
14306
  ],
@@ -16313,7 +16316,7 @@ async function getScraperIntermediateSource(source, options) {
16313
16316
  const { filename: sourceFilename, url } = source;
16314
16317
  const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
16315
16318
  // TODO: [👬] DRY
16316
- const hash = SHA256(
16319
+ const hash = CryptoJS.SHA256(
16317
16320
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
16318
16321
  hexEncoder.parse(sourceFilename || url || 'untitled'))
16319
16322
  .toString( /* hex */)
@@ -17216,7 +17219,7 @@ const _WebsiteScraperMetadataRegistration = $scrapersMetadataRegister.register(w
17216
17219
  * @private utility of `WebsiteScraper`
17217
17220
  */
17218
17221
  function createShowdownConverter() {
17219
- return new Converter({
17222
+ return new showdown.Converter({
17220
17223
  flavor: 'github',
17221
17224
  /*
17222
17225
  > import showdownHighlight from 'showdown-highlight';
@@ -17400,7 +17403,7 @@ function normalizeWhitespaces(sentence) {
17400
17403
  * @public exported from `@promptbook/utils`
17401
17404
  */
17402
17405
  function computeHash(value) {
17403
- return SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
17406
+ return CryptoJS.SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
17404
17407
  }
17405
17408
  // TODO: [🥬][🥬] Use this ACRY
17406
17409
 
@@ -19169,6 +19172,11 @@ class FormatCommitmentDefinition extends BaseCommitmentDefinition {
19169
19172
  * - `isValidAgentUrl` *(this one)* which tests just agent URL
19170
19173
  * - `isValidPipelineUrl` which tests just pipeline URL
19171
19174
  *
19175
+ * Note: This is a pure structural validator and does not block private/internal network
19176
+ * addresses. Callers that fetch the URL server-side from an untrusted network context must
19177
+ * additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
19178
+ * outbound request.
19179
+ *
19172
19180
  * @public exported from `@promptbook/utils`
19173
19181
  */
19174
19182
  function isValidAgentUrl(url) {
@@ -19182,12 +19190,6 @@ function isValidAgentUrl(url) {
19182
19190
  // TODO: [🐠]
19183
19191
  return false;
19184
19192
  }
19185
- /*
19186
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
19187
- if (isUrlOnPrivateNetwork(url)) {
19188
- return false;
19189
- }
19190
- */
19191
19193
  return true;
19192
19194
  }
19193
19195
  // TODO: [🐠] Maybe more info why the URL is invalid
@@ -19363,8 +19365,11 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
19363
19365
  if (!trimmedContent) {
19364
19366
  return requirements;
19365
19367
  }
19366
- // Add goal as a proper h2 section to the system message
19367
- const goalSection = `## Goal\n\n${trimmedContent}`;
19368
+ const goalSection = spaceTrim$1((block) => `
19369
+ ## Goal
19370
+
19371
+ ${block(trimmedContent)}
19372
+ `);
19368
19373
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
19369
19374
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
19370
19375
  }
@@ -19750,8 +19755,11 @@ class LanguageCommitmentDefinition extends BaseCommitmentDefinition {
19750
19755
  if (!trimmedContent) {
19751
19756
  return requirements;
19752
19757
  }
19753
- // Add language as a bullet under a ## Language section
19754
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
19758
+ const languageSection = spaceTrim$1((block) => `
19759
+ ## Language
19760
+
19761
+ - Your language is ${block(trimmedContent)}
19762
+ `);
19755
19763
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
19756
19764
  }
19757
19765
  }
@@ -22094,21 +22102,22 @@ function getPointBounds(points) {
22094
22102
  * @private helper of `fractalAvatarVisual`
22095
22103
  */
22096
22104
  function drawDragonCurveLayer(context, points, options) {
22097
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
22105
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
22098
22106
  const firstPoint = points[0];
22099
22107
  const lastPoint = points[points.length - 1];
22100
22108
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
22101
22109
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
22102
22110
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
22103
22111
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
22112
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
22113
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
22104
22114
  context.save();
22105
22115
  context.beginPath();
22106
22116
  tracePolyline(context, points);
22107
- context.strokeStyle = `${shadowColor}82`;
22108
- context.lineWidth = strokeWidth * 1.8;
22117
+ context.strokeStyle = `${shadowColor}48`;
22118
+ context.lineWidth = strokeWidth * 4.5;
22109
22119
  context.lineJoin = 'round';
22110
22120
  context.lineCap = 'round';
22111
- context.filter = `blur(${size * 0.022}px)`;
22112
22121
  context.stroke();
22113
22122
  context.restore();
22114
22123
  context.beginPath();
@@ -22570,7 +22579,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
22570
22579
  *
22571
22580
  * @private helper of `minecraft2AvatarVisual`
22572
22581
  */
22573
- const LIGHT_DIRECTION$2 = normalizeVector3({
22582
+ const LIGHT_DIRECTION$4 = normalizeVector3({
22574
22583
  x: 0.4,
22575
22584
  y: -0.65,
22576
22585
  z: 0.92,
@@ -22692,11 +22701,23 @@ function drawMinecraftBackdrop(context, size, palette, sceneCenterX, spotlightY,
22692
22701
  * @private helper of `minecraft2AvatarVisual`
22693
22702
  */
22694
22703
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
22704
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
22705
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
22706
+ const rx = size * (0.16 + interaction.intensity * 0.015);
22707
+ const ry = size * 0.055;
22708
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
22695
22709
  context.save();
22696
- context.fillStyle = `${palette.shadow}66`;
22697
- context.filter = `blur(${size * 0.02}px)`;
22710
+ context.translate(cx, cy);
22711
+ context.scale(1, ry / rx);
22712
+ const blurRadius = rx * 1.4;
22713
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
22714
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
22715
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
22716
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
22717
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
22718
+ context.fillStyle = shadowGradient;
22698
22719
  context.beginPath();
22699
- 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);
22720
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
22700
22721
  context.fill();
22701
22722
  context.restore();
22702
22723
  }
@@ -22782,7 +22803,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
22782
22803
  corners: projectedCorners,
22783
22804
  texture: faceDefinition.texture,
22784
22805
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
22785
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
22806
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
22786
22807
  outlineColor: cuboid.outlineColor,
22787
22808
  };
22788
22809
  });
@@ -22920,13 +22941,27 @@ const minecraftAvatarVisual = {
22920
22941
  spotlight.addColorStop(1, `${palette.highlight}00`);
22921
22942
  context.fillStyle = spotlight;
22922
22943
  context.fillRect(0, 0, size, size);
22923
- context.save();
22924
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
22925
- context.filter = `blur(${size * 0.018}px)`;
22926
- context.beginPath();
22927
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
22928
- context.fill();
22929
- context.restore();
22944
+ {
22945
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
22946
+ const cx = size * 0.5;
22947
+ const cy = size * 0.86;
22948
+ const rx = size * 0.2;
22949
+ const ry = size * 0.06;
22950
+ const blurRadius = rx * 1.4;
22951
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
22952
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
22953
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
22954
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
22955
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
22956
+ context.save();
22957
+ context.translate(cx, cy);
22958
+ context.scale(1, ry / rx);
22959
+ context.fillStyle = shadowGradient;
22960
+ context.beginPath();
22961
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
22962
+ context.fill();
22963
+ context.restore();
22964
+ }
22930
22965
  drawVoxelCuboid(context, {
22931
22966
  x: bodyX,
22932
22967
  y: bodyY,
@@ -23455,7 +23490,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
23455
23490
  *
23456
23491
  * @private helper of `octopus3AvatarVisual`
23457
23492
  */
23458
- function formatAlphaHex$1(opacity) {
23493
+ function formatAlphaHex$2(opacity) {
23459
23494
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
23460
23495
  .toString(16)
23461
23496
  .padStart(2, '0');
@@ -23834,7 +23869,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
23834
23869
  context.beginPath();
23835
23870
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
23836
23871
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
23837
- context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
23872
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
23838
23873
  context.lineWidth = radiusX * 0.08;
23839
23874
  context.lineCap = 'round';
23840
23875
  context.stroke();
@@ -23915,7 +23950,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
23915
23950
  context.beginPath();
23916
23951
  context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23917
23952
  context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23918
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
23953
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
23919
23954
  context.lineWidth = projectedRadiusX * 0.08;
23920
23955
  context.lineCap = 'round';
23921
23956
  context.stroke();
@@ -23961,7 +23996,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
23961
23996
  *
23962
23997
  * @private helper of the 3D octopus avatar visuals
23963
23998
  */
23964
- function formatAlphaHex(opacity) {
23999
+ function formatAlphaHex$1(opacity) {
23965
24000
  return Math.round(clampNumber$1(opacity, 0, 1) * 255)
23966
24001
  .toString(16)
23967
24002
  .padStart(2, '0');
@@ -23973,11 +24008,40 @@ function formatAlphaHex(opacity) {
23973
24008
  *
23974
24009
  * @private helper of `octopus3dAvatarVisual`
23975
24010
  */
23976
- const LIGHT_DIRECTION$1 = normalizeVector3({
24011
+ const LIGHT_DIRECTION$3 = normalizeVector3({
23977
24012
  x: 0.48,
23978
24013
  y: -0.62,
23979
24014
  z: 0.94,
23980
24015
  });
24016
+ /**
24017
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
24018
+ *
24019
+ * @private helper of `octopus3dAvatarVisual`
24020
+ */
24021
+ const octopus3dStableStateCache = new WeakMap();
24022
+ /**
24023
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
24024
+ *
24025
+ * @private helper of `octopus3dAvatarVisual`
24026
+ */
24027
+ function getOctopus3dStableState(createRandom) {
24028
+ const cached = octopus3dStableStateCache.get(createRandom);
24029
+ if (cached !== undefined) {
24030
+ return cached;
24031
+ }
24032
+ const animationRandom = createRandom('octopus3d-animation-profile');
24033
+ const eyeRandom = createRandom('octopus3d-eye-profile');
24034
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
24035
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
24036
+ const state = {
24037
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
24038
+ animationPhase: animationRandom() * Math.PI * 2,
24039
+ leftEyePhaseOffset,
24040
+ rightEyePhaseOffset,
24041
+ };
24042
+ octopus3dStableStateCache.set(createRandom, state);
24043
+ return state;
24044
+ }
23981
24045
  /**
23982
24046
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
23983
24047
  *
@@ -23990,10 +24054,7 @@ const octopus3dAvatarVisual = {
23990
24054
  isAnimated: true,
23991
24055
  supportsPointerTracking: true,
23992
24056
  render({ context, size, palette, createRandom, timeMs, interaction }) {
23993
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
23994
- const animationRandom = createRandom('octopus3d-animation-profile');
23995
- const eyeRandom = createRandom('octopus3d-eye-profile');
23996
- const animationPhase = animationRandom() * Math.PI * 2;
24057
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
23997
24058
  const sceneCenterX = size * 0.5;
23998
24059
  const sceneCenterY = size * 0.56;
23999
24060
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -24090,12 +24151,12 @@ const octopus3dAvatarVisual = {
24090
24151
  x: -faceEyeSpacing,
24091
24152
  y: faceEyeYOffset,
24092
24153
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
24093
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
24154
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24094
24155
  drawProjectedOrganicEye(context, {
24095
24156
  x: faceEyeSpacing,
24096
24157
  y: faceEyeYOffset,
24097
24158
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
24098
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
24159
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24099
24160
  drawProjectedOrganicMouth(context, [
24100
24161
  {
24101
24162
  x: -mouthHalfWidth,
@@ -24139,14 +24200,28 @@ function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCent
24139
24200
  /**
24140
24201
  * Draws the soft ground shadow below the octopus.
24141
24202
  *
24203
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24204
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24205
+ *
24142
24206
  * @private helper of `octopus3dAvatarVisual`
24143
24207
  */
24144
24208
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
24209
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
24210
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
24211
+ const rx = size * (0.18 + interaction.intensity * 0.02);
24212
+ const ry = size * 0.06;
24145
24213
  context.save();
24146
- context.fillStyle = `${palette.shadow}66`;
24147
- context.filter = `blur(${size * 0.022}px)`;
24214
+ context.translate(cx, cy);
24215
+ context.scale(1, ry / rx);
24216
+ const blurRadius = rx * 1.4;
24217
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
24218
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
24219
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
24220
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
24221
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
24222
+ context.fillStyle = shadowGradient;
24148
24223
  context.beginPath();
24149
- 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);
24224
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
24150
24225
  context.fill();
24151
24226
  context.restore();
24152
24227
  }
@@ -24183,7 +24258,7 @@ function resolveVisibleEllipsoidPatches(options) {
24183
24258
  corners: projectedCorners,
24184
24259
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
24185
24260
  transformedCorners.length,
24186
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
24261
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
24187
24262
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
24188
24263
  outlineColor,
24189
24264
  });
@@ -24372,11 +24447,40 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
24372
24447
  *
24373
24448
  * @private helper of `octopus3d2AvatarVisual`
24374
24449
  */
24375
- const LIGHT_DIRECTION = normalizeVector3({
24450
+ const LIGHT_DIRECTION$2 = normalizeVector3({
24376
24451
  x: 0.38,
24377
24452
  y: -0.6,
24378
24453
  z: 0.98,
24379
24454
  });
24455
+ /**
24456
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
24457
+ *
24458
+ * @private helper of `octopus3d2AvatarVisual`
24459
+ */
24460
+ const octopus3d2StableStateCache = new WeakMap();
24461
+ /**
24462
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
24463
+ *
24464
+ * @private helper of `octopus3d2AvatarVisual`
24465
+ */
24466
+ function getOctopus3d2StableState(createRandom) {
24467
+ const cached = octopus3d2StableStateCache.get(createRandom);
24468
+ if (cached !== undefined) {
24469
+ return cached;
24470
+ }
24471
+ const animationRandom = createRandom('octopus3d2-animation-profile');
24472
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
24473
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
24474
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
24475
+ const state = {
24476
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
24477
+ animationPhase: animationRandom() * Math.PI * 2,
24478
+ leftEyePhaseOffset,
24479
+ rightEyePhaseOffset,
24480
+ };
24481
+ octopus3d2StableStateCache.set(createRandom, state);
24482
+ return state;
24483
+ }
24380
24484
  /**
24381
24485
  * Octopus 3D 2 avatar visual.
24382
24486
  *
@@ -24389,10 +24493,7 @@ const octopus3d2AvatarVisual = {
24389
24493
  isAnimated: true,
24390
24494
  supportsPointerTracking: true,
24391
24495
  render({ context, size, palette, createRandom, timeMs, interaction }) {
24392
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24393
- const animationRandom = createRandom('octopus3d2-animation-profile');
24394
- const eyeRandom = createRandom('octopus3d2-eye-profile');
24395
- const animationPhase = animationRandom() * Math.PI * 2;
24496
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
24396
24497
  const sceneCenterX = size * 0.5;
24397
24498
  const sceneCenterY = size * 0.575;
24398
24499
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -24445,8 +24546,8 @@ const octopus3d2AvatarVisual = {
24445
24546
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
24446
24547
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
24447
24548
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
24448
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24449
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24549
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24550
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24450
24551
  drawProjectedOrganicMouth(context, [
24451
24552
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24452
24553
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -24475,54 +24576,118 @@ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, scene
24475
24576
  /**
24476
24577
  * Draws the soft floor shadow that anchors the single mesh in the frame.
24477
24578
  *
24579
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24580
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24581
+ *
24478
24582
  * @private helper of `octopus3d2AvatarVisual`
24479
24583
  */
24480
24584
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
24585
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
24586
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
24587
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
24588
+ const ry = size * 0.062;
24481
24589
  context.save();
24482
- context.fillStyle = `${palette.shadow}66`;
24483
- context.filter = `blur(${size * 0.024}px)`;
24590
+ context.translate(cx, cy);
24591
+ context.scale(1, ry / rx);
24592
+ const blurRadius = rx * 1.4;
24593
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
24594
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
24595
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
24596
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
24597
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
24598
+ context.fillStyle = shadowGradient;
24484
24599
  context.beginPath();
24485
- 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);
24600
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
24486
24601
  context.fill();
24487
24602
  context.restore();
24488
24603
  }
24604
+ /**
24605
+ * Number of latitude segments used by the single blobby octopus mesh.
24606
+ *
24607
+ * @private helper of `octopus3d2AvatarVisual`
24608
+ */
24609
+ const LATITUDE_PATCH_COUNT$2 = 12;
24610
+ /**
24611
+ * Number of longitude segments used by the single blobby octopus mesh.
24612
+ *
24613
+ * @private helper of `octopus3d2AvatarVisual`
24614
+ */
24615
+ const LONGITUDE_PATCH_COUNT$2 = 24;
24489
24616
  /**
24490
24617
  * Resolves all visible projected patches for the single blobby octopus mesh.
24491
24618
  *
24619
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
24620
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
24621
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
24622
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
24623
+ * corners are shared between neighboring patches.
24624
+ *
24492
24625
  * @private helper of `octopus3d2AvatarVisual`
24493
24626
  */
24494
24627
  function resolveVisibleBlobbyOctopusPatches(options) {
24495
24628
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
24496
- const latitudePatchCount = 12;
24497
- const longitudePatchCount = 24;
24629
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
24630
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
24498
24631
  const surfacePatches = [];
24632
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
24633
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
24634
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
24635
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
24636
+ }
24637
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
24638
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
24639
+ }
24640
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
24641
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
24642
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
24643
+ }
24644
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
24645
+ const transformedCornerSamples = new Array(cornerCount);
24646
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
24647
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
24648
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
24649
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
24650
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
24651
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
24652
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
24653
+ }
24654
+ }
24499
24655
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
24500
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
24501
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
24656
+ const startLatitude = latitudeBoundaries[latitudeIndex];
24657
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
24502
24658
  const centerLatitude = (startLatitude + endLatitude) / 2;
24503
24659
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
24660
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
24661
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
24504
24662
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
24505
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
24506
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
24663
+ const startLongitude = longitudeBoundaries[longitudeIndex];
24664
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
24507
24665
  const centerLongitude = (startLongitude + endLongitude) / 2;
24508
- const localCorners = [
24509
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
24510
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
24511
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
24512
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
24666
+ const transformedCorners = [
24667
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
24668
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
24669
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
24670
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
24513
24671
  ];
24514
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
24515
24672
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
24516
24673
  if (surfaceNormal.z <= 0.01) {
24517
24674
  continue;
24518
24675
  }
24519
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
24676
+ const projectedCorners = [
24677
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
24678
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
24679
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
24680
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
24681
+ ];
24520
24682
  surfacePatches.push({
24521
24683
  corners: projectedCorners,
24522
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
24523
- transformedCorners.length,
24524
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
24525
- fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
24684
+ averageDepth: (transformedCorners[0].z +
24685
+ transformedCorners[1].z +
24686
+ transformedCorners[2].z +
24687
+ transformedCorners[3].z) /
24688
+ 4,
24689
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
24690
+ fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
24526
24691
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
24527
24692
  });
24528
24693
  }
@@ -24538,12 +24703,21 @@ function resolveVisibleBlobbyOctopusPatches(options) {
24538
24703
  * @private helper of `octopus3d2AvatarVisual`
24539
24704
  */
24540
24705
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
24706
+ const { morphologyProfile, animationPhase, timeMs } = options;
24707
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
24708
+ }
24709
+ /**
24710
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
24711
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
24712
+ *
24713
+ * @private helper of `octopus3d2AvatarVisual`
24714
+ */
24715
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
24541
24716
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
24542
24717
  const cosineLatitude = Math.max(0, Math.cos(latitude));
24543
24718
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
24544
24719
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
24545
24720
  const lowerBlend = Math.pow(verticalProgress, 1.42);
24546
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
24547
24721
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
24548
24722
  const horizontalScale = 1.02 +
24549
24723
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -24581,7 +24755,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
24581
24755
  *
24582
24756
  * @private helper of `octopus3d2AvatarVisual`
24583
24757
  */
24584
- function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
24758
+ function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
24585
24759
  const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
24586
24760
  if (tonalProgress < 0.16) {
24587
24761
  return palette.highlight;
@@ -24621,6 +24795,1384 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
24621
24795
  context.restore();
24622
24796
  }
24623
24797
 
24798
+ /* eslint-disable no-magic-numbers */
24799
+ /**
24800
+ * Light direction used by the continuous octopus mesh shading.
24801
+ *
24802
+ * @private helper of `octopus3d3AvatarVisual`
24803
+ */
24804
+ const LIGHT_DIRECTION$1 = normalizeVector3({
24805
+ x: 0.34,
24806
+ y: -0.62,
24807
+ z: 1,
24808
+ });
24809
+ /**
24810
+ * Real-octopus tentacle count used by the continuous lower mesh.
24811
+ *
24812
+ * @private helper of `octopus3d3AvatarVisual`
24813
+ */
24814
+ const OCTOPUS_TENTACLE_COUNT$1 = 8;
24815
+ /**
24816
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
24817
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
24818
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
24819
+ *
24820
+ * @private helper of `octopus3d3AvatarVisual`
24821
+ */
24822
+ const stableStateCache = new WeakMap();
24823
+ /**
24824
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
24825
+ * result on every subsequent call within the same `<Avatar/>` mount.
24826
+ *
24827
+ * @private helper of `octopus3d3AvatarVisual`
24828
+ */
24829
+ function getOctopus3d3StableState(createRandom) {
24830
+ const cached = stableStateCache.get(createRandom);
24831
+ if (cached !== undefined) {
24832
+ return cached;
24833
+ }
24834
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24835
+ const animationRandom = createRandom('octopus3d3-animation-profile');
24836
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
24837
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
24838
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
24839
+ const state = {
24840
+ morphologyProfile,
24841
+ animationPhase: animationRandom() * Math.PI * 2,
24842
+ leftEyePhaseOffset,
24843
+ rightEyePhaseOffset,
24844
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
24845
+ };
24846
+ stableStateCache.set(createRandom, state);
24847
+ return state;
24848
+ }
24849
+ /**
24850
+ * Octopus 3D 3 avatar visual.
24851
+ *
24852
+ * @private built-in avatar visual
24853
+ */
24854
+ const octopus3d3AvatarVisual = {
24855
+ id: 'octopus3d3',
24856
+ title: 'Octopus 3D 3',
24857
+ description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
24858
+ isAnimated: true,
24859
+ supportsPointerTracking: true,
24860
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
24861
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
24862
+ const sceneCenterX = size * 0.5;
24863
+ const sceneCenterY = size * 0.535;
24864
+ const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
24865
+ const meshCenter = {
24866
+ x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
24867
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
24868
+ z: interaction.intensity * size * 0.018,
24869
+ };
24870
+ const rotationY = -0.1 +
24871
+ Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
24872
+ interaction.bodyOffsetX * 0.22 +
24873
+ interaction.gazeX * 0.88;
24874
+ const rotationX = -0.07 +
24875
+ Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
24876
+ interaction.bodyOffsetY * 0.08 -
24877
+ interaction.gazeY * 0.38;
24878
+ const surfaceOptions = {
24879
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
24880
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
24881
+ radiusZ: size *
24882
+ morphologyProfile.body.bodyRadiusRatio *
24883
+ (1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
24884
+ morphologyProfile,
24885
+ timeMs,
24886
+ animationPhase,
24887
+ tentacleProfiles,
24888
+ };
24889
+ const surfacePatches = resolveVisibleContinuousOctopusPatches({
24890
+ ...surfaceOptions,
24891
+ center: meshCenter,
24892
+ rotationX,
24893
+ rotationY,
24894
+ sceneCenterX,
24895
+ sceneCenterY,
24896
+ size,
24897
+ palette,
24898
+ });
24899
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
24900
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
24901
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
24902
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
24903
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
24904
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
24905
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
24906
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
24907
+ drawAvatarFrame(context, size, palette);
24908
+ drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
24909
+ drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
24910
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
24911
+ drawContinuousSurfacePatch(context, surfacePatch);
24912
+ }
24913
+ drawProjectedSurfaceCurrents({
24914
+ context,
24915
+ surfaceOptions,
24916
+ center: meshCenter,
24917
+ rotationX,
24918
+ rotationY,
24919
+ sceneCenterX,
24920
+ sceneCenterY,
24921
+ size,
24922
+ palette,
24923
+ morphologyProfile,
24924
+ timeMs,
24925
+ animationPhase,
24926
+ });
24927
+ drawProjectedTentacleSuckers({
24928
+ context,
24929
+ surfaceOptions,
24930
+ center: meshCenter,
24931
+ rotationX,
24932
+ rotationY,
24933
+ sceneCenterX,
24934
+ sceneCenterY,
24935
+ size,
24936
+ palette,
24937
+ });
24938
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24939
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24940
+ drawProjectedOrganicMouth(context, [
24941
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24942
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
24943
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
24944
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
24945
+ },
24946
+ };
24947
+ /**
24948
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
24949
+ *
24950
+ * @private helper of `octopus3d3AvatarVisual`
24951
+ */
24952
+ function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
24953
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
24954
+ const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
24955
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
24956
+ return {
24957
+ centerLongitude: -Math.PI * 0.86 +
24958
+ progress * Math.PI * 1.72 +
24959
+ (tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
24960
+ widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
24961
+ lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
24962
+ swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
24963
+ depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
24964
+ phase: tentacleRandom() * Math.PI * 2,
24965
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
24966
+ };
24967
+ });
24968
+ }
24969
+ /**
24970
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
24971
+ *
24972
+ * @private helper of `octopus3d3AvatarVisual`
24973
+ */
24974
+ function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
24975
+ 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));
24976
+ glowGradient.addColorStop(0, `${palette.highlight}66`);
24977
+ glowGradient.addColorStop(0.34, `${palette.accent}2e`);
24978
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
24979
+ context.fillStyle = glowGradient;
24980
+ context.fillRect(0, 0, size, size);
24981
+ 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);
24982
+ lowerGradient.addColorStop(0, `${palette.secondary}25`);
24983
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
24984
+ context.fillStyle = lowerGradient;
24985
+ context.fillRect(0, 0, size, size);
24986
+ }
24987
+ /**
24988
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
24989
+ *
24990
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24991
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24992
+ *
24993
+ * @private helper of `octopus3d3AvatarVisual`
24994
+ */
24995
+ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
24996
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
24997
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
24998
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
24999
+ const ry = size * 0.06;
25000
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
25001
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
25002
+ context.save();
25003
+ context.translate(cx, cy);
25004
+ context.scale(1, ry / rx);
25005
+ const blurRadius = rx * 1.4;
25006
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
25007
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
25008
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
25009
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
25010
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
25011
+ context.fillStyle = shadowGradient;
25012
+ context.beginPath();
25013
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
25014
+ context.fill();
25015
+ context.restore();
25016
+ }
25017
+ /**
25018
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
25019
+ *
25020
+ * @private helper of `octopus3d3AvatarVisual`
25021
+ */
25022
+ const LATITUDE_PATCH_COUNT$1 = 16;
25023
+ /**
25024
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
25025
+ *
25026
+ * @private helper of `octopus3d3AvatarVisual`
25027
+ */
25028
+ const LONGITUDE_PATCH_COUNT$1 = 40;
25029
+ /**
25030
+ * Resolves visible projected patches for the continuous octopus mesh.
25031
+ *
25032
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
25033
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
25034
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
25035
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
25036
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
25037
+ *
25038
+ * @private helper of `octopus3d3AvatarVisual`
25039
+ */
25040
+ function resolveVisibleContinuousOctopusPatches(options) {
25041
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
25042
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
25043
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
25044
+ const surfacePatches = [];
25045
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
25046
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
25047
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
25048
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
25049
+ }
25050
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25051
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
25052
+ }
25053
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
25054
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
25055
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
25056
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25057
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25058
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25059
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
25060
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
25061
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
25062
+ }
25063
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25064
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
25065
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
25066
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
25067
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
25068
+ }
25069
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
25070
+ const transformedCornerSamples = new Array(cornerCount);
25071
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
25072
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
25073
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
25074
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
25075
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
25076
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
25077
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
25078
+ }
25079
+ }
25080
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
25081
+ const startLatitude = latitudeBoundaries[latitudeIndex];
25082
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
25083
+ const centerLatitude = (startLatitude + endLatitude) / 2;
25084
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
25085
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
25086
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
25087
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25088
+ const transformedCorners = [
25089
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
25090
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
25091
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
25092
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
25093
+ ];
25094
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
25095
+ if (surfaceNormal.z <= 0.008) {
25096
+ continue;
25097
+ }
25098
+ const projectedCorners = [
25099
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
25100
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
25101
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
25102
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
25103
+ ];
25104
+ surfacePatches.push({
25105
+ corners: projectedCorners,
25106
+ averageDepth: (transformedCorners[0].z +
25107
+ transformedCorners[1].z +
25108
+ transformedCorners[2].z +
25109
+ transformedCorners[3].z) /
25110
+ 4,
25111
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
25112
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
25113
+ outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
25114
+ });
25115
+ }
25116
+ }
25117
+ return surfacePatches;
25118
+ }
25119
+ /**
25120
+ * Samples one point on the continuous Octopus 3D 3 surface.
25121
+ *
25122
+ * The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
25123
+ * tentacled while still being rendered as one connected blobby mesh.
25124
+ *
25125
+ * @private helper of `octopus3d3AvatarVisual`
25126
+ */
25127
+ function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
25128
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
25129
+ }
25130
+ /**
25131
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
25132
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
25133
+ *
25134
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
25135
+ * corner grid, so the same longitude is reused across every latitude row and each
25136
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
25137
+ *
25138
+ * @private helper of `octopus3d3AvatarVisual`
25139
+ */
25140
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
25141
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
25142
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
25143
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
25144
+ const upperBlend = Math.pow(1 - verticalProgress, 1.28);
25145
+ const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
25146
+ const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
25147
+ const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
25148
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
25149
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
25150
+ animationPhase * 0.6 +
25151
+ timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
25152
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
25153
+ (0.3 + lowerBlend * 0.7);
25154
+ const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
25155
+ lowerBlend *
25156
+ tentacleInfluence.core *
25157
+ tentacleInfluence.swayScale;
25158
+ const horizontalScale = 1.04 +
25159
+ mantleRipple +
25160
+ lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
25161
+ lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
25162
+ upperBlend * 0.08;
25163
+ const depthScale = 1.06 +
25164
+ upperBlend * 0.16 +
25165
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
25166
+ lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
25167
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
25168
+ const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
25169
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
25170
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
25171
+ const lowerDrop = lowerBlend *
25172
+ radiusY *
25173
+ (0.18 +
25174
+ tentacleInfluence.core *
25175
+ (0.38 +
25176
+ tentacleInfluence.lengthScale * 0.22 +
25177
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
25178
+ return {
25179
+ x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
25180
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
25181
+ upperBlend * radiusY * 0.1 +
25182
+ lowerDrop +
25183
+ Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
25184
+ Math.cos(timeMs / 880 + tentacleInfluence.phase) *
25185
+ lowerBlend *
25186
+ tipBlend *
25187
+ tentacleInfluence.core *
25188
+ radiusY *
25189
+ 0.034,
25190
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
25191
+ Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
25192
+ lowerBlend *
25193
+ tentacleInfluence.core *
25194
+ radiusZ *
25195
+ 0.04,
25196
+ };
25197
+ }
25198
+ /**
25199
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
25200
+ *
25201
+ * @private helper of `octopus3d3AvatarVisual`
25202
+ */
25203
+ function resolveContinuousTentacleInfluence(options, longitude) {
25204
+ let totalWeight = 0;
25205
+ let weightedSin = 0;
25206
+ let weightedCos = 0;
25207
+ let weightedWidthScale = 0;
25208
+ let weightedLengthScale = 0;
25209
+ let weightedSwayScale = 0;
25210
+ let weightedDepthScale = 0;
25211
+ let weightedPhase = 0;
25212
+ for (const tentacleProfile of options.tentacleProfiles) {
25213
+ const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
25214
+ const width = 0.2 * tentacleProfile.widthScale;
25215
+ const weight = Math.exp(-(distance * distance) / (width * width));
25216
+ totalWeight += weight;
25217
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
25218
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
25219
+ weightedWidthScale += tentacleProfile.widthScale * weight;
25220
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
25221
+ weightedSwayScale += tentacleProfile.swayScale * weight;
25222
+ weightedDepthScale += tentacleProfile.depthScale * weight;
25223
+ weightedPhase += tentacleProfile.phase * weight;
25224
+ }
25225
+ if (totalWeight < 0.0001) {
25226
+ return {
25227
+ core: 0,
25228
+ centerLongitude: longitude,
25229
+ widthScale: 1,
25230
+ lengthScale: 1,
25231
+ swayScale: 1,
25232
+ depthScale: 1,
25233
+ phase: 0,
25234
+ };
25235
+ }
25236
+ return {
25237
+ core: clampNumber$1(totalWeight, 0, 1),
25238
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
25239
+ widthScale: weightedWidthScale / totalWeight,
25240
+ lengthScale: weightedLengthScale / totalWeight,
25241
+ swayScale: weightedSwayScale / totalWeight,
25242
+ depthScale: weightedDepthScale / totalWeight,
25243
+ phase: weightedPhase / totalWeight,
25244
+ };
25245
+ }
25246
+ /**
25247
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
25248
+ *
25249
+ * @private helper of `octopus3d3AvatarVisual`
25250
+ */
25251
+ function resolveContinuousLobeWave(options, longitude) {
25252
+ const { morphologyProfile, animationPhase, timeMs } = options;
25253
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
25254
+ animationPhase +
25255
+ timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
25256
+ 1) /
25257
+ 2);
25258
+ }
25259
+ /**
25260
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
25261
+ *
25262
+ * @private helper of `octopus3d3AvatarVisual`
25263
+ */
25264
+ function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
25265
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
25266
+ if (tonalProgress < 0.14) {
25267
+ return palette.highlight;
25268
+ }
25269
+ if (tonalProgress < 0.32) {
25270
+ return palette.secondary;
25271
+ }
25272
+ if (tonalProgress < 0.72) {
25273
+ return forwardness > 0.55 ? palette.secondary : palette.primary;
25274
+ }
25275
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
25276
+ }
25277
+ /**
25278
+ * Draws one projected mesh patch with soft shading and a subtle edge.
25279
+ *
25280
+ * @private helper of `octopus3d3AvatarVisual`
25281
+ */
25282
+ function drawContinuousSurfacePatch(context, surfacePatch) {
25283
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
25284
+ if (surfacePatch.lightIntensity > 0) {
25285
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
25286
+ }
25287
+ else if (surfacePatch.lightIntensity < 0) {
25288
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
25289
+ }
25290
+ context.save();
25291
+ context.beginPath();
25292
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
25293
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
25294
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
25295
+ }
25296
+ context.closePath();
25297
+ context.strokeStyle = surfacePatch.outlineColor;
25298
+ context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
25299
+ context.lineJoin = 'round';
25300
+ context.stroke();
25301
+ context.restore();
25302
+ }
25303
+ /**
25304
+ * Draws projected mantle-current lines on the front of the mesh.
25305
+ *
25306
+ * @private helper of `octopus3d3AvatarVisual`
25307
+ */
25308
+ function drawProjectedSurfaceCurrents(options) {
25309
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
25310
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
25311
+ const centerIndex = (currentCount - 1) / 2;
25312
+ context.save();
25313
+ context.lineCap = 'round';
25314
+ context.lineJoin = 'round';
25315
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
25316
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
25317
+ const projectedPoints = [];
25318
+ for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
25319
+ const progress = sampleIndex / 7;
25320
+ const latitude = -0.46 + progress * 0.74;
25321
+ const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
25322
+ const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
25323
+ if (scenePoint.z > center.z - size * 0.016) {
25324
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
25325
+ }
25326
+ }
25327
+ if (projectedPoints.length < 3) {
25328
+ continue;
25329
+ }
25330
+ context.beginPath();
25331
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
25332
+ for (const projectedPoint of projectedPoints.slice(1)) {
25333
+ context.lineTo(projectedPoint.x, projectedPoint.y);
25334
+ }
25335
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
25336
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
25337
+ context.stroke();
25338
+ }
25339
+ context.restore();
25340
+ }
25341
+ /**
25342
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
25343
+ *
25344
+ * @private helper of `octopus3d3AvatarVisual`
25345
+ */
25346
+ function drawProjectedTentacleSuckers(options) {
25347
+ const { surfaceOptions, size } = options;
25348
+ const { timeMs } = surfaceOptions;
25349
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
25350
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
25351
+ continue;
25352
+ }
25353
+ for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
25354
+ const latitude = 0.52 + suckerIndex * 0.14;
25355
+ const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
25356
+ const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
25357
+ drawProjectedSurfaceSpot({
25358
+ ...options,
25359
+ latitude,
25360
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
25361
+ radiusScale: size * (0.0065 - suckerIndex * 0.0007),
25362
+ });
25363
+ }
25364
+ }
25365
+ }
25366
+ /**
25367
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
25368
+ *
25369
+ * @private helper of `octopus3d3AvatarVisual`
25370
+ */
25371
+ function drawProjectedSurfaceSpot(options) {
25372
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
25373
+ const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
25374
+ const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
25375
+ const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
25376
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
25377
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
25378
+ return;
25379
+ }
25380
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
25381
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
25382
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
25383
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
25384
+ radiusScale *
25385
+ 0.74, size * 0.003, size * 0.018);
25386
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
25387
+ radiusScale *
25388
+ 0.52, size * 0.0024, size * 0.014);
25389
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
25390
+ context.save();
25391
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
25392
+ context.rotate(rotation);
25393
+ context.beginPath();
25394
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
25395
+ context.fillStyle = `${palette.highlight}73`;
25396
+ context.fill();
25397
+ context.strokeStyle = `${palette.highlight}99`;
25398
+ context.lineWidth = Math.max(0.7, size * 0.0028);
25399
+ context.stroke();
25400
+ context.restore();
25401
+ }
25402
+ /**
25403
+ * Resolves a signed angular distance from the source longitude to the target longitude.
25404
+ *
25405
+ * @private helper of `octopus3d3AvatarVisual`
25406
+ */
25407
+ function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
25408
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
25409
+ }
25410
+ /**
25411
+ * Smoothly maps a value between two bounds into `[0, 1]`.
25412
+ *
25413
+ * @private helper of `octopus3d3AvatarVisual`
25414
+ */
25415
+ function smoothStep$1(edgeStart, edgeEnd, value) {
25416
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
25417
+ return progress * progress * (3 - 2 * progress);
25418
+ }
25419
+
25420
+ /* eslint-disable no-magic-numbers */
25421
+ /**
25422
+ * Light direction used by the continuous Octopus 3D 4 mesh shading.
25423
+ *
25424
+ * @private helper of `octopus3d4AvatarVisual`
25425
+ */
25426
+ const LIGHT_DIRECTION = normalizeVector3({
25427
+ x: 0.32,
25428
+ y: -0.66,
25429
+ z: 1,
25430
+ });
25431
+ /**
25432
+ * Rim-light direction used to brighten the side of the silhouette.
25433
+ *
25434
+ * @private helper of `octopus3d4AvatarVisual`
25435
+ */
25436
+ const RIM_LIGHT_DIRECTION = normalizeVector3({
25437
+ x: -0.7,
25438
+ y: -0.18,
25439
+ z: 0.55,
25440
+ });
25441
+ /**
25442
+ * Real-octopus tentacle count used by the continuous lower mesh.
25443
+ *
25444
+ * @private helper of `octopus3d4AvatarVisual`
25445
+ */
25446
+ const OCTOPUS_TENTACLE_COUNT = 8;
25447
+ /**
25448
+ * Number of seeded skin spots painted across the octopus body.
25449
+ *
25450
+ * @private helper of `octopus3d4AvatarVisual`
25451
+ */
25452
+ const SKIN_SPOT_COUNT = 14;
25453
+ /**
25454
+ * Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
25455
+ *
25456
+ * @private helper of `octopus3d4AvatarVisual`
25457
+ */
25458
+ const octopus3d4StableStateCache = new WeakMap();
25459
+ /**
25460
+ * Returns the stable per-avatar state, computing it on first access and caching afterwards.
25461
+ *
25462
+ * @private helper of `octopus3d4AvatarVisual`
25463
+ */
25464
+ function getOctopus3d4StableState(createRandom) {
25465
+ const cached = octopus3d4StableStateCache.get(createRandom);
25466
+ if (cached !== undefined) {
25467
+ return cached;
25468
+ }
25469
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
25470
+ const animationRandom = createRandom('octopus3d4-animation-profile');
25471
+ const eyeRandom = createRandom('octopus3d4-eye-profile');
25472
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
25473
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
25474
+ const state = {
25475
+ morphologyProfile,
25476
+ animationPhase: animationRandom() * Math.PI * 2,
25477
+ leftEyePhaseOffset,
25478
+ rightEyePhaseOffset,
25479
+ tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
25480
+ skinSpots: createBlobbySkinSpots(createRandom),
25481
+ };
25482
+ octopus3d4StableStateCache.set(createRandom, state);
25483
+ return state;
25484
+ }
25485
+ /**
25486
+ * Octopus 3D 4 avatar visual.
25487
+ *
25488
+ * @private built-in avatar visual
25489
+ */
25490
+ const octopus3d4AvatarVisual = {
25491
+ id: 'octopus3d4',
25492
+ title: 'Octopus 3D 4',
25493
+ 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.',
25494
+ isAnimated: true,
25495
+ supportsPointerTracking: true,
25496
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
25497
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
25498
+ const sceneCenterX = size * 0.5;
25499
+ const sceneCenterY = size * 0.535;
25500
+ const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
25501
+ const meshCenter = {
25502
+ x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
25503
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
25504
+ z: interaction.intensity * size * 0.02,
25505
+ };
25506
+ const rotationY = -0.08 +
25507
+ Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
25508
+ interaction.bodyOffsetX * 0.24 +
25509
+ interaction.gazeX * 0.98;
25510
+ const rotationX = -0.07 +
25511
+ Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
25512
+ interaction.bodyOffsetY * 0.08 -
25513
+ interaction.gazeY * 0.42;
25514
+ const surfaceOptions = {
25515
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
25516
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
25517
+ radiusZ: size *
25518
+ morphologyProfile.body.bodyRadiusRatio *
25519
+ (1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
25520
+ morphologyProfile,
25521
+ timeMs,
25522
+ animationPhase,
25523
+ tentacleProfiles,
25524
+ };
25525
+ const surfacePatches = resolveVisibleBlobbyContinuousPatches({
25526
+ ...surfaceOptions,
25527
+ center: meshCenter,
25528
+ rotationX,
25529
+ rotationY,
25530
+ sceneCenterX,
25531
+ sceneCenterY,
25532
+ size,
25533
+ palette,
25534
+ });
25535
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
25536
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
25537
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
25538
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
25539
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
25540
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
25541
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
25542
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
25543
+ drawAvatarFrame(context, size, palette);
25544
+ drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
25545
+ drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
25546
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
25547
+ drawBlobbyContinuousSurfacePatch(context, surfacePatch);
25548
+ }
25549
+ drawBlobbySkinSpots({
25550
+ context,
25551
+ surfaceOptions,
25552
+ center: meshCenter,
25553
+ rotationX,
25554
+ rotationY,
25555
+ sceneCenterX,
25556
+ sceneCenterY,
25557
+ size,
25558
+ palette,
25559
+ skinSpots,
25560
+ });
25561
+ drawBlobbyContinuousCurrents({
25562
+ context,
25563
+ surfaceOptions,
25564
+ center: meshCenter,
25565
+ rotationX,
25566
+ rotationY,
25567
+ sceneCenterX,
25568
+ sceneCenterY,
25569
+ size,
25570
+ palette,
25571
+ morphologyProfile,
25572
+ timeMs,
25573
+ animationPhase,
25574
+ });
25575
+ drawBlobbyContinuousSuckers({
25576
+ context,
25577
+ surfaceOptions,
25578
+ center: meshCenter,
25579
+ rotationX,
25580
+ rotationY,
25581
+ sceneCenterX,
25582
+ sceneCenterY,
25583
+ size,
25584
+ palette,
25585
+ });
25586
+ drawBlobbyContinuousGloss({
25587
+ context,
25588
+ surfaceOptions,
25589
+ center: meshCenter,
25590
+ rotationX,
25591
+ rotationY,
25592
+ sceneCenterX,
25593
+ sceneCenterY,
25594
+ size,
25595
+ palette,
25596
+ });
25597
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
25598
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
25599
+ drawProjectedOrganicMouth(context, [
25600
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
25601
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
25602
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
25603
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
25604
+ },
25605
+ };
25606
+ /**
25607
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
25608
+ *
25609
+ * @private helper of `octopus3d4AvatarVisual`
25610
+ */
25611
+ function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
25612
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
25613
+ const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
25614
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
25615
+ return {
25616
+ centerLongitude: -Math.PI * 0.9 +
25617
+ progress * Math.PI * 1.8 +
25618
+ (tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
25619
+ widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
25620
+ lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
25621
+ swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
25622
+ depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
25623
+ curlScale: 0.62 + tentacleRandom() * 0.46,
25624
+ primaryPhase: tentacleRandom() * Math.PI * 2,
25625
+ secondaryPhase: tentacleRandom() * Math.PI * 2,
25626
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
25627
+ };
25628
+ });
25629
+ }
25630
+ /**
25631
+ * Creates seeded skin pigment spots distributed across the upper octopus mesh.
25632
+ *
25633
+ * @private helper of `octopus3d4AvatarVisual`
25634
+ */
25635
+ function createBlobbySkinSpots(createRandom) {
25636
+ const spotRandom = createRandom('octopus3d4-skin-spots');
25637
+ return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
25638
+ latitude: -0.45 + spotRandom() * 0.7,
25639
+ longitude: -0.5 + spotRandom() * 1.0,
25640
+ radiusScale: 0.0028 + spotRandom() * 0.0052,
25641
+ opacity: 0.16 + spotRandom() * 0.22,
25642
+ }));
25643
+ }
25644
+ /**
25645
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
25646
+ *
25647
+ * @private helper of `octopus3d4AvatarVisual`
25648
+ */
25649
+ function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
25650
+ 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));
25651
+ glowGradient.addColorStop(0, `${palette.highlight}74`);
25652
+ glowGradient.addColorStop(0.32, `${palette.accent}32`);
25653
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
25654
+ context.fillStyle = glowGradient;
25655
+ context.fillRect(0, 0, size, size);
25656
+ 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);
25657
+ lowerGradient.addColorStop(0, `${palette.secondary}28`);
25658
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
25659
+ context.fillStyle = lowerGradient;
25660
+ context.fillRect(0, 0, size, size);
25661
+ }
25662
+ /**
25663
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
25664
+ *
25665
+ * @private helper of `octopus3d4AvatarVisual`
25666
+ */
25667
+ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
25668
+ const cx = size * 0.5 + interaction.gazeX * size * 0.046;
25669
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
25670
+ const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
25671
+ const ry = size * 0.062;
25672
+ context.save();
25673
+ context.translate(cx, cy);
25674
+ context.scale(1, ry / rx);
25675
+ const blurRadius = rx * 1.42;
25676
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
25677
+ shadowGradient.addColorStop(0, `${palette.shadow}82`);
25678
+ shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
25679
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
25680
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
25681
+ context.fillStyle = shadowGradient;
25682
+ context.beginPath();
25683
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
25684
+ context.fill();
25685
+ context.restore();
25686
+ }
25687
+ /**
25688
+ * Number of latitude segments used by the continuous Octopus 3D 4 mesh.
25689
+ *
25690
+ * @private helper of `octopus3d4AvatarVisual`
25691
+ */
25692
+ const LATITUDE_PATCH_COUNT = 20;
25693
+ /**
25694
+ * Number of longitude segments used by the continuous Octopus 3D 4 mesh.
25695
+ *
25696
+ * @private helper of `octopus3d4AvatarVisual`
25697
+ */
25698
+ const LONGITUDE_PATCH_COUNT = 48;
25699
+ /**
25700
+ * Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
25701
+ *
25702
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
25703
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
25704
+ * than re-evaluated for every patch corner.
25705
+ *
25706
+ * @private helper of `octopus3d4AvatarVisual`
25707
+ */
25708
+ function resolveVisibleBlobbyContinuousPatches(options) {
25709
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
25710
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
25711
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
25712
+ const surfacePatches = [];
25713
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
25714
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
25715
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
25716
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
25717
+ }
25718
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25719
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
25720
+ }
25721
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
25722
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
25723
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
25724
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25725
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
25726
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
25727
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
25728
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
25729
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
25730
+ }
25731
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25732
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
25733
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
25734
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
25735
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
25736
+ }
25737
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
25738
+ const transformedCornerSamples = new Array(cornerCount);
25739
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
25740
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
25741
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
25742
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
25743
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
25744
+ const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
25745
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
25746
+ }
25747
+ }
25748
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
25749
+ const startLatitude = latitudeBoundaries[latitudeIndex];
25750
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
25751
+ const centerLatitude = (startLatitude + endLatitude) / 2;
25752
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
25753
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
25754
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
25755
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
25756
+ const transformedCorners = [
25757
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
25758
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
25759
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
25760
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
25761
+ ];
25762
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
25763
+ if (surfaceNormal.z <= 0.006) {
25764
+ continue;
25765
+ }
25766
+ const projectedCorners = [
25767
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
25768
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
25769
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
25770
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
25771
+ ];
25772
+ const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
25773
+ const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
25774
+ surfacePatches.push({
25775
+ corners: projectedCorners,
25776
+ averageDepth: (transformedCorners[0].z +
25777
+ transformedCorners[1].z +
25778
+ transformedCorners[2].z +
25779
+ transformedCorners[3].z) /
25780
+ 4,
25781
+ lightIntensity,
25782
+ rimLightIntensity,
25783
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
25784
+ outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
25785
+ });
25786
+ }
25787
+ }
25788
+ return surfacePatches;
25789
+ }
25790
+ /**
25791
+ * Samples one point on the continuous Octopus 3D 4 surface.
25792
+ *
25793
+ * @private helper of `octopus3d4AvatarVisual`
25794
+ */
25795
+ function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
25796
+ return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
25797
+ }
25798
+ /**
25799
+ * Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
25800
+ *
25801
+ * The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
25802
+ * across every latitude row and each tentacle/lobe value is computed once per frame instead
25803
+ * of `latitudePatchCount * 4` times.
25804
+ *
25805
+ * @private helper of `octopus3d4AvatarVisual`
25806
+ */
25807
+ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
25808
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
25809
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
25810
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
25811
+ const upperBlend = Math.pow(1 - verticalProgress, 1.32);
25812
+ const lowerBlend = smoothStep(0.34, 1, verticalProgress);
25813
+ const tipBlend = smoothStep(0.66, 1, verticalProgress);
25814
+ const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
25815
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
25816
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
25817
+ animationPhase * 0.6 +
25818
+ timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
25819
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
25820
+ (0.32 + lowerBlend * 0.7);
25821
+ const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
25822
+ lowerBlend *
25823
+ tentacleInfluence.core *
25824
+ tentacleInfluence.swayScale;
25825
+ const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
25826
+ lowerBlend *
25827
+ tentacleInfluence.core *
25828
+ tentacleInfluence.swayScale *
25829
+ 0.42;
25830
+ const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
25831
+ tipBlend *
25832
+ tentacleInfluence.core *
25833
+ tentacleInfluence.curlScale *
25834
+ 0.36;
25835
+ const horizontalScale = 1.06 +
25836
+ mantleRipple +
25837
+ lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
25838
+ lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
25839
+ upperBlend * 0.08;
25840
+ const depthScale = 1.08 +
25841
+ upperBlend * 0.16 +
25842
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
25843
+ lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
25844
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
25845
+ const tentacleTubeRadius = lowerBlend *
25846
+ tentacleInfluence.core *
25847
+ (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
25848
+ radiusX;
25849
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
25850
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
25851
+ const lowerDrop = lowerBlend *
25852
+ radiusY *
25853
+ (0.2 +
25854
+ tentacleInfluence.core *
25855
+ (0.42 +
25856
+ tentacleInfluence.lengthScale * 0.24 +
25857
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
25858
+ const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
25859
+ return {
25860
+ x: Math.sin(effectiveLongitude) * planarRadiusX +
25861
+ combinedTentacleSway +
25862
+ tentacleCurl * radiusX * 0.18,
25863
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
25864
+ upperBlend * radiusY * 0.12 +
25865
+ lowerDrop +
25866
+ Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
25867
+ Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
25868
+ lowerBlend *
25869
+ tipBlend *
25870
+ tentacleInfluence.core *
25871
+ radiusY *
25872
+ 0.04,
25873
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
25874
+ Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
25875
+ lowerBlend *
25876
+ tentacleInfluence.core *
25877
+ radiusZ *
25878
+ 0.044 +
25879
+ tentacleCurl * radiusZ * 0.14,
25880
+ };
25881
+ }
25882
+ /**
25883
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
25884
+ *
25885
+ * @private helper of `octopus3d4AvatarVisual`
25886
+ */
25887
+ function resolveBlobbyTentacleInfluence(options, longitude) {
25888
+ let totalWeight = 0;
25889
+ let weightedSin = 0;
25890
+ let weightedCos = 0;
25891
+ let weightedWidthScale = 0;
25892
+ let weightedLengthScale = 0;
25893
+ let weightedSwayScale = 0;
25894
+ let weightedDepthScale = 0;
25895
+ let weightedCurlScale = 0;
25896
+ let weightedPrimaryPhase = 0;
25897
+ let weightedSecondaryPhase = 0;
25898
+ for (const tentacleProfile of options.tentacleProfiles) {
25899
+ const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
25900
+ const width = 0.22 * tentacleProfile.widthScale;
25901
+ const weight = Math.exp(-(distance * distance) / (width * width));
25902
+ totalWeight += weight;
25903
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
25904
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
25905
+ weightedWidthScale += tentacleProfile.widthScale * weight;
25906
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
25907
+ weightedSwayScale += tentacleProfile.swayScale * weight;
25908
+ weightedDepthScale += tentacleProfile.depthScale * weight;
25909
+ weightedCurlScale += tentacleProfile.curlScale * weight;
25910
+ weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
25911
+ weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
25912
+ }
25913
+ if (totalWeight < 0.0001) {
25914
+ return {
25915
+ core: 0,
25916
+ centerLongitude: longitude,
25917
+ widthScale: 1,
25918
+ lengthScale: 1,
25919
+ swayScale: 1,
25920
+ depthScale: 1,
25921
+ curlScale: 1,
25922
+ primaryPhase: 0,
25923
+ secondaryPhase: 0,
25924
+ };
25925
+ }
25926
+ return {
25927
+ core: clampNumber$1(totalWeight, 0, 1),
25928
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
25929
+ widthScale: weightedWidthScale / totalWeight,
25930
+ lengthScale: weightedLengthScale / totalWeight,
25931
+ swayScale: weightedSwayScale / totalWeight,
25932
+ depthScale: weightedDepthScale / totalWeight,
25933
+ curlScale: weightedCurlScale / totalWeight,
25934
+ primaryPhase: weightedPrimaryPhase / totalWeight,
25935
+ secondaryPhase: weightedSecondaryPhase / totalWeight,
25936
+ };
25937
+ }
25938
+ /**
25939
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
25940
+ *
25941
+ * @private helper of `octopus3d4AvatarVisual`
25942
+ */
25943
+ function resolveBlobbyLobeWave(options, longitude) {
25944
+ const { morphologyProfile, animationPhase, timeMs } = options;
25945
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
25946
+ animationPhase +
25947
+ timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
25948
+ 1) /
25949
+ 2);
25950
+ }
25951
+ /**
25952
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
25953
+ *
25954
+ * @private helper of `octopus3d4AvatarVisual`
25955
+ */
25956
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
25957
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
25958
+ if (tonalProgress < 0.12) {
25959
+ return palette.highlight;
25960
+ }
25961
+ if (tonalProgress < 0.3) {
25962
+ return palette.secondary;
25963
+ }
25964
+ if (tonalProgress < 0.72) {
25965
+ return forwardness > 0.56 ? palette.secondary : palette.primary;
25966
+ }
25967
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
25968
+ }
25969
+ /**
25970
+ * Draws one projected mesh patch with multi-pass shading and a soft edge.
25971
+ *
25972
+ * @private helper of `octopus3d4AvatarVisual`
25973
+ */
25974
+ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
25975
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
25976
+ if (surfacePatch.lightIntensity > 0) {
25977
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
25978
+ }
25979
+ else if (surfacePatch.lightIntensity < 0) {
25980
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
25981
+ }
25982
+ if (surfacePatch.rimLightIntensity > 0.04) {
25983
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
25984
+ }
25985
+ context.save();
25986
+ context.beginPath();
25987
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
25988
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
25989
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
25990
+ }
25991
+ context.closePath();
25992
+ context.strokeStyle = surfacePatch.outlineColor;
25993
+ context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
25994
+ context.lineJoin = 'round';
25995
+ context.stroke();
25996
+ context.restore();
25997
+ }
25998
+ /**
25999
+ * Draws projected mantle-current lines on the front of the mesh.
26000
+ *
26001
+ * @private helper of `octopus3d4AvatarVisual`
26002
+ */
26003
+ function drawBlobbyContinuousCurrents(options) {
26004
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
26005
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
26006
+ const centerIndex = (currentCount - 1) / 2;
26007
+ context.save();
26008
+ context.lineCap = 'round';
26009
+ context.lineJoin = 'round';
26010
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
26011
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
26012
+ const projectedPoints = [];
26013
+ for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
26014
+ const progress = sampleIndex / 8;
26015
+ const latitude = -0.48 + progress * 0.78;
26016
+ const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
26017
+ const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
26018
+ if (scenePoint.z > center.z - size * 0.016) {
26019
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
26020
+ }
26021
+ }
26022
+ if (projectedPoints.length < 3) {
26023
+ continue;
26024
+ }
26025
+ context.beginPath();
26026
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
26027
+ for (const projectedPoint of projectedPoints.slice(1)) {
26028
+ context.lineTo(projectedPoint.x, projectedPoint.y);
26029
+ }
26030
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
26031
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
26032
+ context.stroke();
26033
+ }
26034
+ context.restore();
26035
+ }
26036
+ /**
26037
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
26038
+ *
26039
+ * @private helper of `octopus3d4AvatarVisual`
26040
+ */
26041
+ function drawBlobbyContinuousSuckers(options) {
26042
+ const { surfaceOptions, size } = options;
26043
+ const { timeMs } = surfaceOptions;
26044
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
26045
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
26046
+ continue;
26047
+ }
26048
+ for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
26049
+ const latitude = 0.5 + suckerIndex * 0.12;
26050
+ const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
26051
+ const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
26052
+ drawBlobbyContinuousSurfaceSpot({
26053
+ ...options,
26054
+ latitude,
26055
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
26056
+ radiusScale: size * (0.0068 - suckerIndex * 0.0006),
26057
+ });
26058
+ }
26059
+ }
26060
+ }
26061
+ /**
26062
+ * Draws seeded pigment spots across the upper mesh for a richer skin texture.
26063
+ *
26064
+ * @private helper of `octopus3d4AvatarVisual`
26065
+ */
26066
+ function drawBlobbySkinSpots(options) {
26067
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
26068
+ for (const skinSpot of skinSpots) {
26069
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
26070
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
26071
+ if (sceneCenterPoint.z <= center.z - size * 0.01) {
26072
+ continue;
26073
+ }
26074
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
26075
+ const spotRadius = size * skinSpot.radiusScale;
26076
+ context.save();
26077
+ context.beginPath();
26078
+ context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
26079
+ context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
26080
+ context.fill();
26081
+ context.restore();
26082
+ }
26083
+ }
26084
+ /**
26085
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
26086
+ *
26087
+ * @private helper of `octopus3d4AvatarVisual`
26088
+ */
26089
+ function drawBlobbyContinuousSurfaceSpot(options) {
26090
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
26091
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
26092
+ const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
26093
+ const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
26094
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
26095
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
26096
+ return;
26097
+ }
26098
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
26099
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
26100
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
26101
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
26102
+ radiusScale *
26103
+ 0.78, size * 0.003, size * 0.02);
26104
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
26105
+ radiusScale *
26106
+ 0.54, size * 0.0024, size * 0.015);
26107
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
26108
+ context.save();
26109
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
26110
+ context.rotate(rotation);
26111
+ context.beginPath();
26112
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
26113
+ context.fillStyle = `${palette.highlight}80`;
26114
+ context.fill();
26115
+ context.strokeStyle = `${palette.highlight}a8`;
26116
+ context.lineWidth = Math.max(0.7, size * 0.0028);
26117
+ context.stroke();
26118
+ context.restore();
26119
+ }
26120
+ /**
26121
+ * Draws a soft, slowly drifting gloss highlight on the front of the mesh.
26122
+ *
26123
+ * @private helper of `octopus3d4AvatarVisual`
26124
+ */
26125
+ function drawBlobbyContinuousGloss(options) {
26126
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
26127
+ const { timeMs, animationPhase } = surfaceOptions;
26128
+ const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
26129
+ const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
26130
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
26131
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
26132
+ if (sceneCenterPoint.z <= center.z) {
26133
+ return;
26134
+ }
26135
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
26136
+ const glossRadius = size * 0.058;
26137
+ context.save();
26138
+ const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
26139
+ glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
26140
+ glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
26141
+ glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
26142
+ context.fillStyle = glossGradient;
26143
+ context.beginPath();
26144
+ context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
26145
+ context.fill();
26146
+ context.restore();
26147
+ }
26148
+ /**
26149
+ * Resolves a signed angular distance from the source longitude to the target longitude.
26150
+ *
26151
+ * @private helper of `octopus3d4AvatarVisual`
26152
+ */
26153
+ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
26154
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
26155
+ }
26156
+ /**
26157
+ * Smoothly maps a value between two bounds into `[0, 1]`.
26158
+ *
26159
+ * @private helper of `octopus3d4AvatarVisual`
26160
+ */
26161
+ function smoothStep(edgeStart, edgeEnd, value) {
26162
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
26163
+ return progress * progress * (3 - 2 * progress);
26164
+ }
26165
+ /**
26166
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
26167
+ *
26168
+ * @private helper of `octopus3d4AvatarVisual`
26169
+ */
26170
+ function formatAlphaHex(opacity) {
26171
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
26172
+ .toString(16)
26173
+ .padStart(2, '0');
26174
+ }
26175
+
24624
26176
  /* eslint-disable no-magic-numbers */
24625
26177
  /**
24626
26178
  * Octopus avatar visual.
@@ -25391,6 +26943,8 @@ const AVATAR_VISUALS = [
25391
26943
  octopus3AvatarVisual,
25392
26944
  octopus3dAvatarVisual,
25393
26945
  octopus3d2AvatarVisual,
26946
+ octopus3d3AvatarVisual,
26947
+ octopus3d4AvatarVisual,
25394
26948
  asciiOctopusAvatarVisual,
25395
26949
  minecraftAvatarVisual,
25396
26950
  minecraft2AvatarVisual,
@@ -27679,7 +29233,15 @@ function buildTeammateMetadata(entry) {
27679
29233
  * Builds the teammate request text, optionally including context.
27680
29234
  */
27681
29235
  function buildTeammateRequest(message, context) {
27682
- return context ? `${message}\n\nContext:\n${context}` : message;
29236
+ if (!context) {
29237
+ return message;
29238
+ }
29239
+ return spaceTrim$1((block) => `
29240
+ ${block(message)}
29241
+
29242
+ Context:
29243
+ ${block(context)}
29244
+ `);
27683
29245
  }
27684
29246
  /**
27685
29247
  * Builds a minimal chat prompt for teammate calls.
@@ -31505,7 +33067,11 @@ function createUseProjectToolFunctions() {
31505
33067
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
31506
33068
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
31507
33069
  const contentToReturn = wasCharacterTruncated
31508
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
33070
+ ? spaceTrim$1((block) => `
33071
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
33072
+
33073
+ [...truncated...]
33074
+ `)
31509
33075
  : lineRangedContent;
31510
33076
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
31511
33077
  if (wasCharacterTruncated) {
@@ -34994,11 +36560,19 @@ const JsonFileImportPlugin = {
34994
36560
  try {
34995
36561
  const json = JSON.parse(content);
34996
36562
  const formattedJson = JSON.stringify(json, null, 4);
34997
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
36563
+ return spaceTrim$1((block) => `
36564
+ \`\`\`json
36565
+ ${block(formattedJson)}
36566
+ \`\`\`
36567
+ `);
34998
36568
  }
34999
36569
  catch (error) {
35000
36570
  // If JSON is invalid, still import it but maybe not as pretty JSON
35001
- return `\`\`\`json\n${content}\n\`\`\``;
36571
+ return spaceTrim$1((block) => `
36572
+ \`\`\`json
36573
+ ${block(content)}
36574
+ \`\`\`
36575
+ `);
35002
36576
  }
35003
36577
  },
35004
36578
  };
@@ -35022,7 +36596,11 @@ const TextFileImportPlugin = {
35022
36596
  import(content, mimeType) {
35023
36597
  const extension = mimeTypeToExtension(mimeType);
35024
36598
  const codeBlockType = extension || 'txt';
35025
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
36599
+ return spaceTrim$1((block) => `
36600
+ \`\`\`${codeBlockType}
36601
+ ${block(content)}
36602
+ \`\`\`
36603
+ `);
35026
36604
  },
35027
36605
  };
35028
36606
 
@@ -35269,11 +36847,19 @@ function collectExampleInteractionLines(parseResult, samples) {
35269
36847
  const examples = [];
35270
36848
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
35271
36849
  if (initialMessage) {
35272
- examples.push(`**Agent:**\n${initialMessage}`);
36850
+ examples.push(spaceTrim$1((block) => `
36851
+ **Agent:**
36852
+ ${block(initialMessage)}
36853
+ `));
35273
36854
  }
35274
36855
  if (samples && samples.length > 0) {
35275
36856
  for (const sample of samples) {
35276
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
36857
+ examples.push(spaceTrim$1((block) => `
36858
+ **User:** ${block(String(sample.question))}
36859
+
36860
+ **Agent:**
36861
+ ${block(sample.answer)}
36862
+ `));
35277
36863
  }
35278
36864
  }
35279
36865
  return examples;
@@ -39798,7 +41384,12 @@ function colorizeAvailableProviderMessage(providerMessage, { isInstalled, isFull
39798
41384
  * @private internal function of `$registeredLlmToolsMessage`
39799
41385
  */
39800
41386
  function createUsedEnvMessage() {
39801
- return $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
41387
+ return $usedEnvFilename === null
41388
+ ? `Unknown \`.env\` file`
41389
+ : spaceTrim$1(`
41390
+ Used \`.env\` file:
41391
+ ${$usedEnvFilename}
41392
+ `);
39802
41393
  }
39803
41394
  // TODO: [®] DRY Register logic
39804
41395
  // TODO: [🧠][⚛] Maybe pass env as argument
@@ -40338,7 +41929,7 @@ function cacheLlmTools(llmTools, options = {}) {
40338
41929
  *
40339
41930
  * It looks for environment variables:
40340
41931
  * - `process.env.OPENAI_API_KEY`
40341
- * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
41932
+ * - `process.env.ANTHROPIC_API_KEY` (or the deprecated `process.env.ANTHROPIC_CLAUDE_API_KEY`)
40342
41933
  * - ...
40343
41934
  *
40344
41935
  * @see Environment variables documentation or .env file for required variables.
@@ -40438,7 +42029,7 @@ function createLlmToolsFromConfiguration(configuration, options = {}) {
40438
42029
  *
40439
42030
  * It looks for environment variables:
40440
42031
  * - `process.env.OPENAI_API_KEY`
40441
- * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
42032
+ * - `process.env.ANTHROPIC_API_KEY` (or the deprecated `process.env.ANTHROPIC_CLAUDE_API_KEY`)
40442
42033
  * - ...
40443
42034
  *
40444
42035
  * @param options Configuration options for the LLM tools
@@ -48868,7 +50459,9 @@ class OpenAiAgentKitExecutionToolsToolBuilder {
48868
50459
  if (!executionTools || !executionTools.script) {
48869
50460
  throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
48870
50461
  }
48871
- return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
50462
+ return Array.isArray(executionTools.script)
50463
+ ? executionTools.script
50464
+ : [executionTools.script];
48872
50465
  }
48873
50466
  /**
48874
50467
  * Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
@@ -49290,7 +50883,7 @@ function emitAgentLlmExecutionToolsAssistantPreparationProgress(options) {
49290
50883
  * Computes one stable hash from a JSON-serializable value.
49291
50884
  */
49292
50885
  function computeJsonHash$1(value) {
49293
- return SHA256(JSON.stringify(value)).toString();
50886
+ return CryptoJS.SHA256(JSON.stringify(value)).toString();
49294
50887
  }
49295
50888
  /**
49296
50889
  * Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
@@ -49448,7 +51041,7 @@ AgentLlmExecutionToolsAgentKitRunner.vectorStoreCache = new Map();
49448
51041
  * Computes one stable hash from a JSON-serializable value.
49449
51042
  */
49450
51043
  function computeJsonHash(value) {
49451
- return SHA256(JSON.stringify(value)).toString();
51044
+ return CryptoJS.SHA256(JSON.stringify(value)).toString();
49452
51045
  }
49453
51046
  /**
49454
51047
  * Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
@@ -49784,7 +51377,11 @@ function truncateAttachmentInlineText(content, maxCharacters) {
49784
51377
  };
49785
51378
  }
49786
51379
  return {
49787
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
51380
+ content: spaceTrim$1((block) => `
51381
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
51382
+
51383
+ [...truncated...]
51384
+ `),
49788
51385
  isTruncated: true,
49789
51386
  };
49790
51387
  }
@@ -50250,7 +51847,7 @@ class AgentLlmExecutionTools {
50250
51847
  * Returns a virtual model name representing the agent behavior.
50251
51848
  */
50252
51849
  get modelName() {
50253
- const hash = SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
51850
+ const hash = CryptoJS.SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
50254
51851
  const agentId = hash.substring(0, 10);
50255
51852
  return (normalizeToKebabCase(this.title) + '-' + agentId);
50256
51853
  }