@promptbook/remote-server 0.112.0-99 → 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 +1688 -98
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/_packages/components.index.d.ts +4 -0
  5. package/esm/src/_packages/core.index.d.ts +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 +1689 -96
  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
@@ -9,19 +9,19 @@ import { Server } from 'socket.io';
9
9
  import { stat, access, constants, readFile, writeFile, readdir, mkdir, watch, rm } from 'fs/promises';
10
10
  import { Readability } from '@mozilla/readability';
11
11
  import { JSDOM } from 'jsdom';
12
- import { SHA256 } from 'crypto-js';
12
+ import CryptoJS from 'crypto-js';
13
13
  import hexEncoder from 'crypto-js/enc-hex';
14
- import { Converter } from 'showdown';
14
+ import showdown from 'showdown';
15
15
  import * as OpenApiValidator from 'express-openapi-validator';
16
16
  import swaggerUi from 'swagger-ui-express';
17
17
  import { createElement } from 'react';
18
18
  import { renderToStaticMarkup } from 'react-dom/server';
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
23
  import sha256 from 'crypto-js/sha256';
24
- import { parse, unparse } from 'papaparse';
24
+ import papaparse from 'papaparse';
25
25
  import { fileSearchTool, tool, Agent as Agent$1, webSearchTool, run, setDefaultOpenAIClient, setDefaultOpenAIKey } from '@openai/agents';
26
26
  import Bottleneck from 'bottleneck';
27
27
  import OpenAI from 'openai';
@@ -40,7 +40,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
40
40
  * @generated
41
41
  * @see https://github.com/webgptorg/promptbook
42
42
  */
43
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-99';
43
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0';
44
44
  /**
45
45
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
46
46
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1948,7 +1948,7 @@ class ParseError extends Error {
1948
1948
  * @private internal helper function
1949
1949
  */
1950
1950
  function $randomToken(randomness) {
1951
- return randomBytes(randomness).toString('hex');
1951
+ return CryptoJS.lib.WordArray.random(randomness).toString(CryptoJS.enc.Hex);
1952
1952
  }
1953
1953
  // TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
1954
1954
  // TODO: Maybe use nanoid instead https://github.com/ai/nanoid
@@ -5492,7 +5492,7 @@ function normalizeToKebabCase(text) {
5492
5492
  * @public exported from `@promptbook/editable`
5493
5493
  */
5494
5494
  function knowledgeSourceContentToName(knowledgeSourceContent) {
5495
- const hash = SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
5495
+ const hash = CryptoJS.SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
5496
5496
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5497
5497
  .toString( /* hex */)
5498
5498
  .substring(0, 20);
@@ -6486,7 +6486,7 @@ function csvParse(value /* <- TODO: string_csv */, settings, schema /* <- TODO:
6486
6486
  console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
6487
6487
  value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
6488
6488
  }
6489
- const csv = parse(value, settings);
6489
+ const csv = papaparse.parse(value, settings);
6490
6490
  return csv;
6491
6491
  }
6492
6492
 
@@ -6571,10 +6571,10 @@ const CsvFormatParser = {
6571
6571
  i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
6572
6572
  );
6573
6573
  */
6574
- await onProgress(unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
6574
+ await onProgress(papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
6575
6575
  }
6576
6576
  }
6577
- return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6577
+ return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6578
6578
  },
6579
6579
  },
6580
6580
  {
@@ -6602,7 +6602,7 @@ const CsvFormatParser = {
6602
6602
  return /* not await */ mapCallback({ [key]: value }, index, array.length);
6603
6603
  }));
6604
6604
  }));
6605
- return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6605
+ return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6606
6606
  },
6607
6607
  },
6608
6608
  ],
@@ -9259,7 +9259,8 @@ function colorizeAvailableProviderMessage(providerMessage, { isInstalled, isFull
9259
9259
  * @private internal function of `$registeredLlmToolsMessage`
9260
9260
  */
9261
9261
  function createUsedEnvMessage() {
9262
- return `Unknown \`.env\` file` ;
9262
+ return `Unknown \`.env\` file`
9263
+ ;
9263
9264
  }
9264
9265
  // TODO: [®] DRY Register logic
9265
9266
  // TODO: [🧠][⚛] Maybe pass env as argument
@@ -9531,7 +9532,7 @@ async function getScraperIntermediateSource(source, options) {
9531
9532
  const { filename: sourceFilename, url } = source;
9532
9533
  const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
9533
9534
  // TODO: [👬] DRY
9534
- const hash = SHA256(
9535
+ const hash = CryptoJS.SHA256(
9535
9536
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
9536
9537
  hexEncoder.parse(sourceFilename || url || 'untitled'))
9537
9538
  .toString( /* hex */)
@@ -9622,7 +9623,7 @@ $scrapersMetadataRegister.register(websiteScraperMetadata);
9622
9623
  * @private utility of `WebsiteScraper`
9623
9624
  */
9624
9625
  function createShowdownConverter() {
9625
- return new Converter({
9626
+ return new showdown.Converter({
9626
9627
  flavor: 'github',
9627
9628
  /*
9628
9629
  > import showdownHighlight from 'showdown-highlight';
@@ -10814,7 +10815,7 @@ function normalizeWhitespaces(sentence) {
10814
10815
  * @public exported from `@promptbook/utils`
10815
10816
  */
10816
10817
  function computeHash(value) {
10817
- return SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
10818
+ return CryptoJS.SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
10818
10819
  }
10819
10820
  // TODO: [🥬][🥬] Use this ACRY
10820
10821
 
@@ -11056,6 +11057,11 @@ function isVoidPseudoAgentReference(rawReference) {
11056
11057
  * - `isValidAgentUrl` *(this one)* which tests just agent URL
11057
11058
  * - `isValidPipelineUrl` which tests just pipeline URL
11058
11059
  *
11060
+ * Note: This is a pure structural validator and does not block private/internal network
11061
+ * addresses. Callers that fetch the URL server-side from an untrusted network context must
11062
+ * additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
11063
+ * outbound request.
11064
+ *
11059
11065
  * @public exported from `@promptbook/utils`
11060
11066
  */
11061
11067
  function isValidAgentUrl(url) {
@@ -11069,12 +11075,6 @@ function isValidAgentUrl(url) {
11069
11075
  // TODO: [🐠]
11070
11076
  return false;
11071
11077
  }
11072
- /*
11073
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
11074
- if (isUrlOnPrivateNetwork(url)) {
11075
- return false;
11076
- }
11077
- */
11078
11078
  return true;
11079
11079
  }
11080
11080
  // TODO: [🐠] Maybe more info why the URL is invalid
@@ -11250,8 +11250,11 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
11250
11250
  if (!trimmedContent) {
11251
11251
  return requirements;
11252
11252
  }
11253
- // Add goal as a proper h2 section to the system message
11254
- const goalSection = `## Goal\n\n${trimmedContent}`;
11253
+ const goalSection = spaceTrim$1((block) => `
11254
+ ## Goal
11255
+
11256
+ ${block(trimmedContent)}
11257
+ `);
11255
11258
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
11256
11259
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
11257
11260
  }
@@ -11773,8 +11776,11 @@ class LanguageCommitmentDefinition extends BaseCommitmentDefinition {
11773
11776
  if (!trimmedContent) {
11774
11777
  return requirements;
11775
11778
  }
11776
- // Add language as a bullet under a ## Language section
11777
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
11779
+ const languageSection = spaceTrim$1((block) => `
11780
+ ## Language
11781
+
11782
+ - Your language is ${block(trimmedContent)}
11783
+ `);
11778
11784
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
11779
11785
  }
11780
11786
  }
@@ -14240,21 +14246,22 @@ function getPointBounds(points) {
14240
14246
  * @private helper of `fractalAvatarVisual`
14241
14247
  */
14242
14248
  function drawDragonCurveLayer(context, points, options) {
14243
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
14249
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
14244
14250
  const firstPoint = points[0];
14245
14251
  const lastPoint = points[points.length - 1];
14246
14252
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
14247
14253
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
14248
14254
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
14249
14255
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
14256
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
14257
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
14250
14258
  context.save();
14251
14259
  context.beginPath();
14252
14260
  tracePolyline(context, points);
14253
- context.strokeStyle = `${shadowColor}82`;
14254
- context.lineWidth = strokeWidth * 1.8;
14261
+ context.strokeStyle = `${shadowColor}48`;
14262
+ context.lineWidth = strokeWidth * 4.5;
14255
14263
  context.lineJoin = 'round';
14256
14264
  context.lineCap = 'round';
14257
- context.filter = `blur(${size * 0.022}px)`;
14258
14265
  context.stroke();
14259
14266
  context.restore();
14260
14267
  context.beginPath();
@@ -14716,7 +14723,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
14716
14723
  *
14717
14724
  * @private helper of `minecraft2AvatarVisual`
14718
14725
  */
14719
- const LIGHT_DIRECTION$2 = normalizeVector3({
14726
+ const LIGHT_DIRECTION$4 = normalizeVector3({
14720
14727
  x: 0.4,
14721
14728
  y: -0.65,
14722
14729
  z: 0.92,
@@ -14838,11 +14845,23 @@ function drawMinecraftBackdrop(context, size, palette, sceneCenterX, spotlightY,
14838
14845
  * @private helper of `minecraft2AvatarVisual`
14839
14846
  */
14840
14847
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
14848
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
14849
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
14850
+ const rx = size * (0.16 + interaction.intensity * 0.015);
14851
+ const ry = size * 0.055;
14852
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
14841
14853
  context.save();
14842
- context.fillStyle = `${palette.shadow}66`;
14843
- context.filter = `blur(${size * 0.02}px)`;
14854
+ context.translate(cx, cy);
14855
+ context.scale(1, ry / rx);
14856
+ const blurRadius = rx * 1.4;
14857
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
14858
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
14859
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
14860
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
14861
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
14862
+ context.fillStyle = shadowGradient;
14844
14863
  context.beginPath();
14845
- 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);
14864
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
14846
14865
  context.fill();
14847
14866
  context.restore();
14848
14867
  }
@@ -14928,7 +14947,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
14928
14947
  corners: projectedCorners,
14929
14948
  texture: faceDefinition.texture,
14930
14949
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
14931
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
14950
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
14932
14951
  outlineColor: cuboid.outlineColor,
14933
14952
  };
14934
14953
  });
@@ -15066,13 +15085,27 @@ const minecraftAvatarVisual = {
15066
15085
  spotlight.addColorStop(1, `${palette.highlight}00`);
15067
15086
  context.fillStyle = spotlight;
15068
15087
  context.fillRect(0, 0, size, size);
15069
- context.save();
15070
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
15071
- context.filter = `blur(${size * 0.018}px)`;
15072
- context.beginPath();
15073
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
15074
- context.fill();
15075
- context.restore();
15088
+ {
15089
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
15090
+ const cx = size * 0.5;
15091
+ const cy = size * 0.86;
15092
+ const rx = size * 0.2;
15093
+ const ry = size * 0.06;
15094
+ const blurRadius = rx * 1.4;
15095
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15096
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
15097
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
15098
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
15099
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
15100
+ context.save();
15101
+ context.translate(cx, cy);
15102
+ context.scale(1, ry / rx);
15103
+ context.fillStyle = shadowGradient;
15104
+ context.beginPath();
15105
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15106
+ context.fill();
15107
+ context.restore();
15108
+ }
15076
15109
  drawVoxelCuboid(context, {
15077
15110
  x: bodyX,
15078
15111
  y: bodyY,
@@ -15601,7 +15634,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
15601
15634
  *
15602
15635
  * @private helper of `octopus3AvatarVisual`
15603
15636
  */
15604
- function formatAlphaHex$1(opacity) {
15637
+ function formatAlphaHex$2(opacity) {
15605
15638
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
15606
15639
  .toString(16)
15607
15640
  .padStart(2, '0');
@@ -15980,7 +16013,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
15980
16013
  context.beginPath();
15981
16014
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
15982
16015
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
15983
- context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
16016
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
15984
16017
  context.lineWidth = radiusX * 0.08;
15985
16018
  context.lineCap = 'round';
15986
16019
  context.stroke();
@@ -16061,7 +16094,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
16061
16094
  context.beginPath();
16062
16095
  context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
16063
16096
  context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
16064
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
16097
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
16065
16098
  context.lineWidth = projectedRadiusX * 0.08;
16066
16099
  context.lineCap = 'round';
16067
16100
  context.stroke();
@@ -16107,7 +16140,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
16107
16140
  *
16108
16141
  * @private helper of the 3D octopus avatar visuals
16109
16142
  */
16110
- function formatAlphaHex(opacity) {
16143
+ function formatAlphaHex$1(opacity) {
16111
16144
  return Math.round(clampNumber$1(opacity, 0, 1) * 255)
16112
16145
  .toString(16)
16113
16146
  .padStart(2, '0');
@@ -16119,11 +16152,40 @@ function formatAlphaHex(opacity) {
16119
16152
  *
16120
16153
  * @private helper of `octopus3dAvatarVisual`
16121
16154
  */
16122
- const LIGHT_DIRECTION$1 = normalizeVector3({
16155
+ const LIGHT_DIRECTION$3 = normalizeVector3({
16123
16156
  x: 0.48,
16124
16157
  y: -0.62,
16125
16158
  z: 0.94,
16126
16159
  });
16160
+ /**
16161
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
16162
+ *
16163
+ * @private helper of `octopus3dAvatarVisual`
16164
+ */
16165
+ const octopus3dStableStateCache = new WeakMap();
16166
+ /**
16167
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
16168
+ *
16169
+ * @private helper of `octopus3dAvatarVisual`
16170
+ */
16171
+ function getOctopus3dStableState(createRandom) {
16172
+ const cached = octopus3dStableStateCache.get(createRandom);
16173
+ if (cached !== undefined) {
16174
+ return cached;
16175
+ }
16176
+ const animationRandom = createRandom('octopus3d-animation-profile');
16177
+ const eyeRandom = createRandom('octopus3d-eye-profile');
16178
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
16179
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
16180
+ const state = {
16181
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
16182
+ animationPhase: animationRandom() * Math.PI * 2,
16183
+ leftEyePhaseOffset,
16184
+ rightEyePhaseOffset,
16185
+ };
16186
+ octopus3dStableStateCache.set(createRandom, state);
16187
+ return state;
16188
+ }
16127
16189
  /**
16128
16190
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
16129
16191
  *
@@ -16136,10 +16198,7 @@ const octopus3dAvatarVisual = {
16136
16198
  isAnimated: true,
16137
16199
  supportsPointerTracking: true,
16138
16200
  render({ context, size, palette, createRandom, timeMs, interaction }) {
16139
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16140
- const animationRandom = createRandom('octopus3d-animation-profile');
16141
- const eyeRandom = createRandom('octopus3d-eye-profile');
16142
- const animationPhase = animationRandom() * Math.PI * 2;
16201
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
16143
16202
  const sceneCenterX = size * 0.5;
16144
16203
  const sceneCenterY = size * 0.56;
16145
16204
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -16236,12 +16295,12 @@ const octopus3dAvatarVisual = {
16236
16295
  x: -faceEyeSpacing,
16237
16296
  y: faceEyeYOffset,
16238
16297
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
16239
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
16298
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16240
16299
  drawProjectedOrganicEye(context, {
16241
16300
  x: faceEyeSpacing,
16242
16301
  y: faceEyeYOffset,
16243
16302
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
16244
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
16303
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16245
16304
  drawProjectedOrganicMouth(context, [
16246
16305
  {
16247
16306
  x: -mouthHalfWidth,
@@ -16285,14 +16344,28 @@ function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCent
16285
16344
  /**
16286
16345
  * Draws the soft ground shadow below the octopus.
16287
16346
  *
16347
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
16348
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
16349
+ *
16288
16350
  * @private helper of `octopus3dAvatarVisual`
16289
16351
  */
16290
16352
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
16353
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
16354
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
16355
+ const rx = size * (0.18 + interaction.intensity * 0.02);
16356
+ const ry = size * 0.06;
16291
16357
  context.save();
16292
- context.fillStyle = `${palette.shadow}66`;
16293
- context.filter = `blur(${size * 0.022}px)`;
16358
+ context.translate(cx, cy);
16359
+ context.scale(1, ry / rx);
16360
+ const blurRadius = rx * 1.4;
16361
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16362
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16363
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16364
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16365
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16366
+ context.fillStyle = shadowGradient;
16294
16367
  context.beginPath();
16295
- 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);
16368
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16296
16369
  context.fill();
16297
16370
  context.restore();
16298
16371
  }
@@ -16329,7 +16402,7 @@ function resolveVisibleEllipsoidPatches(options) {
16329
16402
  corners: projectedCorners,
16330
16403
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
16331
16404
  transformedCorners.length,
16332
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
16405
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
16333
16406
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
16334
16407
  outlineColor,
16335
16408
  });
@@ -16518,11 +16591,40 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
16518
16591
  *
16519
16592
  * @private helper of `octopus3d2AvatarVisual`
16520
16593
  */
16521
- const LIGHT_DIRECTION = normalizeVector3({
16594
+ const LIGHT_DIRECTION$2 = normalizeVector3({
16522
16595
  x: 0.38,
16523
16596
  y: -0.6,
16524
16597
  z: 0.98,
16525
16598
  });
16599
+ /**
16600
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
16601
+ *
16602
+ * @private helper of `octopus3d2AvatarVisual`
16603
+ */
16604
+ const octopus3d2StableStateCache = new WeakMap();
16605
+ /**
16606
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
16607
+ *
16608
+ * @private helper of `octopus3d2AvatarVisual`
16609
+ */
16610
+ function getOctopus3d2StableState(createRandom) {
16611
+ const cached = octopus3d2StableStateCache.get(createRandom);
16612
+ if (cached !== undefined) {
16613
+ return cached;
16614
+ }
16615
+ const animationRandom = createRandom('octopus3d2-animation-profile');
16616
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
16617
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
16618
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
16619
+ const state = {
16620
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
16621
+ animationPhase: animationRandom() * Math.PI * 2,
16622
+ leftEyePhaseOffset,
16623
+ rightEyePhaseOffset,
16624
+ };
16625
+ octopus3d2StableStateCache.set(createRandom, state);
16626
+ return state;
16627
+ }
16526
16628
  /**
16527
16629
  * Octopus 3D 2 avatar visual.
16528
16630
  *
@@ -16535,10 +16637,7 @@ const octopus3d2AvatarVisual = {
16535
16637
  isAnimated: true,
16536
16638
  supportsPointerTracking: true,
16537
16639
  render({ context, size, palette, createRandom, timeMs, interaction }) {
16538
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16539
- const animationRandom = createRandom('octopus3d2-animation-profile');
16540
- const eyeRandom = createRandom('octopus3d2-eye-profile');
16541
- const animationPhase = animationRandom() * Math.PI * 2;
16640
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
16542
16641
  const sceneCenterX = size * 0.5;
16543
16642
  const sceneCenterY = size * 0.575;
16544
16643
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -16591,8 +16690,8 @@ const octopus3d2AvatarVisual = {
16591
16690
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
16592
16691
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
16593
16692
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
16594
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
16595
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
16693
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16694
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16596
16695
  drawProjectedOrganicMouth(context, [
16597
16696
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
16598
16697
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -16621,54 +16720,118 @@ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, scene
16621
16720
  /**
16622
16721
  * Draws the soft floor shadow that anchors the single mesh in the frame.
16623
16722
  *
16723
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
16724
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
16725
+ *
16624
16726
  * @private helper of `octopus3d2AvatarVisual`
16625
16727
  */
16626
16728
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
16729
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
16730
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
16731
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
16732
+ const ry = size * 0.062;
16627
16733
  context.save();
16628
- context.fillStyle = `${palette.shadow}66`;
16629
- context.filter = `blur(${size * 0.024}px)`;
16734
+ context.translate(cx, cy);
16735
+ context.scale(1, ry / rx);
16736
+ const blurRadius = rx * 1.4;
16737
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16738
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16739
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16740
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16741
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16742
+ context.fillStyle = shadowGradient;
16630
16743
  context.beginPath();
16631
- 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);
16744
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16632
16745
  context.fill();
16633
16746
  context.restore();
16634
16747
  }
16748
+ /**
16749
+ * Number of latitude segments used by the single blobby octopus mesh.
16750
+ *
16751
+ * @private helper of `octopus3d2AvatarVisual`
16752
+ */
16753
+ const LATITUDE_PATCH_COUNT$2 = 12;
16754
+ /**
16755
+ * Number of longitude segments used by the single blobby octopus mesh.
16756
+ *
16757
+ * @private helper of `octopus3d2AvatarVisual`
16758
+ */
16759
+ const LONGITUDE_PATCH_COUNT$2 = 24;
16635
16760
  /**
16636
16761
  * Resolves all visible projected patches for the single blobby octopus mesh.
16637
16762
  *
16763
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
16764
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
16765
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
16766
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
16767
+ * corners are shared between neighboring patches.
16768
+ *
16638
16769
  * @private helper of `octopus3d2AvatarVisual`
16639
16770
  */
16640
16771
  function resolveVisibleBlobbyOctopusPatches(options) {
16641
16772
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
16642
- const latitudePatchCount = 12;
16643
- const longitudePatchCount = 24;
16773
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
16774
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
16644
16775
  const surfacePatches = [];
16776
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
16777
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
16778
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
16779
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
16780
+ }
16781
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16782
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
16783
+ }
16784
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
16785
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16786
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
16787
+ }
16788
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
16789
+ const transformedCornerSamples = new Array(cornerCount);
16790
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
16791
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
16792
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
16793
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
16794
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
16795
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
16796
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
16797
+ }
16798
+ }
16645
16799
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
16646
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
16647
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
16800
+ const startLatitude = latitudeBoundaries[latitudeIndex];
16801
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
16648
16802
  const centerLatitude = (startLatitude + endLatitude) / 2;
16649
16803
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
16804
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
16805
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
16650
16806
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
16651
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
16652
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
16807
+ const startLongitude = longitudeBoundaries[longitudeIndex];
16808
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
16653
16809
  const centerLongitude = (startLongitude + endLongitude) / 2;
16654
- const localCorners = [
16655
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
16656
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
16657
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
16658
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
16810
+ const transformedCorners = [
16811
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
16812
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
16813
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
16814
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
16659
16815
  ];
16660
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
16661
16816
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
16662
16817
  if (surfaceNormal.z <= 0.01) {
16663
16818
  continue;
16664
16819
  }
16665
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
16820
+ const projectedCorners = [
16821
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
16822
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
16823
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
16824
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
16825
+ ];
16666
16826
  surfacePatches.push({
16667
16827
  corners: projectedCorners,
16668
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
16669
- transformedCorners.length,
16670
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
16671
- fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
16828
+ averageDepth: (transformedCorners[0].z +
16829
+ transformedCorners[1].z +
16830
+ transformedCorners[2].z +
16831
+ transformedCorners[3].z) /
16832
+ 4,
16833
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
16834
+ fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
16672
16835
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
16673
16836
  });
16674
16837
  }
@@ -16684,12 +16847,21 @@ function resolveVisibleBlobbyOctopusPatches(options) {
16684
16847
  * @private helper of `octopus3d2AvatarVisual`
16685
16848
  */
16686
16849
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
16850
+ const { morphologyProfile, animationPhase, timeMs } = options;
16851
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
16852
+ }
16853
+ /**
16854
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
16855
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
16856
+ *
16857
+ * @private helper of `octopus3d2AvatarVisual`
16858
+ */
16859
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
16687
16860
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
16688
16861
  const cosineLatitude = Math.max(0, Math.cos(latitude));
16689
16862
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
16690
16863
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
16691
16864
  const lowerBlend = Math.pow(verticalProgress, 1.42);
16692
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
16693
16865
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
16694
16866
  const horizontalScale = 1.02 +
16695
16867
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -16727,7 +16899,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
16727
16899
  *
16728
16900
  * @private helper of `octopus3d2AvatarVisual`
16729
16901
  */
16730
- function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
16902
+ function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
16731
16903
  const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
16732
16904
  if (tonalProgress < 0.16) {
16733
16905
  return palette.highlight;
@@ -16767,6 +16939,1384 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
16767
16939
  context.restore();
16768
16940
  }
16769
16941
 
16942
+ /* eslint-disable no-magic-numbers */
16943
+ /**
16944
+ * Light direction used by the continuous octopus mesh shading.
16945
+ *
16946
+ * @private helper of `octopus3d3AvatarVisual`
16947
+ */
16948
+ const LIGHT_DIRECTION$1 = normalizeVector3({
16949
+ x: 0.34,
16950
+ y: -0.62,
16951
+ z: 1,
16952
+ });
16953
+ /**
16954
+ * Real-octopus tentacle count used by the continuous lower mesh.
16955
+ *
16956
+ * @private helper of `octopus3d3AvatarVisual`
16957
+ */
16958
+ const OCTOPUS_TENTACLE_COUNT$1 = 8;
16959
+ /**
16960
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
16961
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
16962
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
16963
+ *
16964
+ * @private helper of `octopus3d3AvatarVisual`
16965
+ */
16966
+ const stableStateCache = new WeakMap();
16967
+ /**
16968
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
16969
+ * result on every subsequent call within the same `<Avatar/>` mount.
16970
+ *
16971
+ * @private helper of `octopus3d3AvatarVisual`
16972
+ */
16973
+ function getOctopus3d3StableState(createRandom) {
16974
+ const cached = stableStateCache.get(createRandom);
16975
+ if (cached !== undefined) {
16976
+ return cached;
16977
+ }
16978
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16979
+ const animationRandom = createRandom('octopus3d3-animation-profile');
16980
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
16981
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
16982
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
16983
+ const state = {
16984
+ morphologyProfile,
16985
+ animationPhase: animationRandom() * Math.PI * 2,
16986
+ leftEyePhaseOffset,
16987
+ rightEyePhaseOffset,
16988
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
16989
+ };
16990
+ stableStateCache.set(createRandom, state);
16991
+ return state;
16992
+ }
16993
+ /**
16994
+ * Octopus 3D 3 avatar visual.
16995
+ *
16996
+ * @private built-in avatar visual
16997
+ */
16998
+ const octopus3d3AvatarVisual = {
16999
+ id: 'octopus3d3',
17000
+ title: 'Octopus 3D 3',
17001
+ description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
17002
+ isAnimated: true,
17003
+ supportsPointerTracking: true,
17004
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
17005
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
17006
+ const sceneCenterX = size * 0.5;
17007
+ const sceneCenterY = size * 0.535;
17008
+ const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
17009
+ const meshCenter = {
17010
+ x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
17011
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
17012
+ z: interaction.intensity * size * 0.018,
17013
+ };
17014
+ const rotationY = -0.1 +
17015
+ Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
17016
+ interaction.bodyOffsetX * 0.22 +
17017
+ interaction.gazeX * 0.88;
17018
+ const rotationX = -0.07 +
17019
+ Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
17020
+ interaction.bodyOffsetY * 0.08 -
17021
+ interaction.gazeY * 0.38;
17022
+ const surfaceOptions = {
17023
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
17024
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
17025
+ radiusZ: size *
17026
+ morphologyProfile.body.bodyRadiusRatio *
17027
+ (1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
17028
+ morphologyProfile,
17029
+ timeMs,
17030
+ animationPhase,
17031
+ tentacleProfiles,
17032
+ };
17033
+ const surfacePatches = resolveVisibleContinuousOctopusPatches({
17034
+ ...surfaceOptions,
17035
+ center: meshCenter,
17036
+ rotationX,
17037
+ rotationY,
17038
+ sceneCenterX,
17039
+ sceneCenterY,
17040
+ size,
17041
+ palette,
17042
+ });
17043
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
17044
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
17045
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
17046
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
17047
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
17048
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
17049
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
17050
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
17051
+ drawAvatarFrame(context, size, palette);
17052
+ drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
17053
+ drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
17054
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
17055
+ drawContinuousSurfacePatch(context, surfacePatch);
17056
+ }
17057
+ drawProjectedSurfaceCurrents({
17058
+ context,
17059
+ surfaceOptions,
17060
+ center: meshCenter,
17061
+ rotationX,
17062
+ rotationY,
17063
+ sceneCenterX,
17064
+ sceneCenterY,
17065
+ size,
17066
+ palette,
17067
+ morphologyProfile,
17068
+ timeMs,
17069
+ animationPhase,
17070
+ });
17071
+ drawProjectedTentacleSuckers({
17072
+ context,
17073
+ surfaceOptions,
17074
+ center: meshCenter,
17075
+ rotationX,
17076
+ rotationY,
17077
+ sceneCenterX,
17078
+ sceneCenterY,
17079
+ size,
17080
+ palette,
17081
+ });
17082
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17083
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17084
+ drawProjectedOrganicMouth(context, [
17085
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
17086
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
17087
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
17088
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
17089
+ },
17090
+ };
17091
+ /**
17092
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
17093
+ *
17094
+ * @private helper of `octopus3d3AvatarVisual`
17095
+ */
17096
+ function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
17097
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
17098
+ const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
17099
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
17100
+ return {
17101
+ centerLongitude: -Math.PI * 0.86 +
17102
+ progress * Math.PI * 1.72 +
17103
+ (tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
17104
+ widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
17105
+ lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
17106
+ swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
17107
+ depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
17108
+ phase: tentacleRandom() * Math.PI * 2,
17109
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
17110
+ };
17111
+ });
17112
+ }
17113
+ /**
17114
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
17115
+ *
17116
+ * @private helper of `octopus3d3AvatarVisual`
17117
+ */
17118
+ function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
17119
+ 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));
17120
+ glowGradient.addColorStop(0, `${palette.highlight}66`);
17121
+ glowGradient.addColorStop(0.34, `${palette.accent}2e`);
17122
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
17123
+ context.fillStyle = glowGradient;
17124
+ context.fillRect(0, 0, size, size);
17125
+ 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);
17126
+ lowerGradient.addColorStop(0, `${palette.secondary}25`);
17127
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
17128
+ context.fillStyle = lowerGradient;
17129
+ context.fillRect(0, 0, size, size);
17130
+ }
17131
+ /**
17132
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
17133
+ *
17134
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
17135
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
17136
+ *
17137
+ * @private helper of `octopus3d3AvatarVisual`
17138
+ */
17139
+ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
17140
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
17141
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
17142
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
17143
+ const ry = size * 0.06;
17144
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
17145
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
17146
+ context.save();
17147
+ context.translate(cx, cy);
17148
+ context.scale(1, ry / rx);
17149
+ const blurRadius = rx * 1.4;
17150
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
17151
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
17152
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
17153
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
17154
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
17155
+ context.fillStyle = shadowGradient;
17156
+ context.beginPath();
17157
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
17158
+ context.fill();
17159
+ context.restore();
17160
+ }
17161
+ /**
17162
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
17163
+ *
17164
+ * @private helper of `octopus3d3AvatarVisual`
17165
+ */
17166
+ const LATITUDE_PATCH_COUNT$1 = 16;
17167
+ /**
17168
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
17169
+ *
17170
+ * @private helper of `octopus3d3AvatarVisual`
17171
+ */
17172
+ const LONGITUDE_PATCH_COUNT$1 = 40;
17173
+ /**
17174
+ * Resolves visible projected patches for the continuous octopus mesh.
17175
+ *
17176
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
17177
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
17178
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
17179
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
17180
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
17181
+ *
17182
+ * @private helper of `octopus3d3AvatarVisual`
17183
+ */
17184
+ function resolveVisibleContinuousOctopusPatches(options) {
17185
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
17186
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
17187
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
17188
+ const surfacePatches = [];
17189
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
17190
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
17191
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
17192
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
17193
+ }
17194
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17195
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
17196
+ }
17197
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
17198
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
17199
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
17200
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17201
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17202
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17203
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
17204
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
17205
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
17206
+ }
17207
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17208
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
17209
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
17210
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
17211
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
17212
+ }
17213
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
17214
+ const transformedCornerSamples = new Array(cornerCount);
17215
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
17216
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
17217
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
17218
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
17219
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
17220
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
17221
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
17222
+ }
17223
+ }
17224
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
17225
+ const startLatitude = latitudeBoundaries[latitudeIndex];
17226
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
17227
+ const centerLatitude = (startLatitude + endLatitude) / 2;
17228
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
17229
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
17230
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
17231
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17232
+ const transformedCorners = [
17233
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
17234
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
17235
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
17236
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
17237
+ ];
17238
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
17239
+ if (surfaceNormal.z <= 0.008) {
17240
+ continue;
17241
+ }
17242
+ const projectedCorners = [
17243
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
17244
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
17245
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
17246
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
17247
+ ];
17248
+ surfacePatches.push({
17249
+ corners: projectedCorners,
17250
+ averageDepth: (transformedCorners[0].z +
17251
+ transformedCorners[1].z +
17252
+ transformedCorners[2].z +
17253
+ transformedCorners[3].z) /
17254
+ 4,
17255
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
17256
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
17257
+ outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
17258
+ });
17259
+ }
17260
+ }
17261
+ return surfacePatches;
17262
+ }
17263
+ /**
17264
+ * Samples one point on the continuous Octopus 3D 3 surface.
17265
+ *
17266
+ * The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
17267
+ * tentacled while still being rendered as one connected blobby mesh.
17268
+ *
17269
+ * @private helper of `octopus3d3AvatarVisual`
17270
+ */
17271
+ function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
17272
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
17273
+ }
17274
+ /**
17275
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
17276
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
17277
+ *
17278
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
17279
+ * corner grid, so the same longitude is reused across every latitude row and each
17280
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
17281
+ *
17282
+ * @private helper of `octopus3d3AvatarVisual`
17283
+ */
17284
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
17285
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
17286
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
17287
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
17288
+ const upperBlend = Math.pow(1 - verticalProgress, 1.28);
17289
+ const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
17290
+ const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
17291
+ const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
17292
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
17293
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
17294
+ animationPhase * 0.6 +
17295
+ timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
17296
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
17297
+ (0.3 + lowerBlend * 0.7);
17298
+ const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
17299
+ lowerBlend *
17300
+ tentacleInfluence.core *
17301
+ tentacleInfluence.swayScale;
17302
+ const horizontalScale = 1.04 +
17303
+ mantleRipple +
17304
+ lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
17305
+ lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
17306
+ upperBlend * 0.08;
17307
+ const depthScale = 1.06 +
17308
+ upperBlend * 0.16 +
17309
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
17310
+ lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
17311
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
17312
+ const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
17313
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
17314
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
17315
+ const lowerDrop = lowerBlend *
17316
+ radiusY *
17317
+ (0.18 +
17318
+ tentacleInfluence.core *
17319
+ (0.38 +
17320
+ tentacleInfluence.lengthScale * 0.22 +
17321
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
17322
+ return {
17323
+ x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
17324
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
17325
+ upperBlend * radiusY * 0.1 +
17326
+ lowerDrop +
17327
+ Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
17328
+ Math.cos(timeMs / 880 + tentacleInfluence.phase) *
17329
+ lowerBlend *
17330
+ tipBlend *
17331
+ tentacleInfluence.core *
17332
+ radiusY *
17333
+ 0.034,
17334
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
17335
+ Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
17336
+ lowerBlend *
17337
+ tentacleInfluence.core *
17338
+ radiusZ *
17339
+ 0.04,
17340
+ };
17341
+ }
17342
+ /**
17343
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
17344
+ *
17345
+ * @private helper of `octopus3d3AvatarVisual`
17346
+ */
17347
+ function resolveContinuousTentacleInfluence(options, longitude) {
17348
+ let totalWeight = 0;
17349
+ let weightedSin = 0;
17350
+ let weightedCos = 0;
17351
+ let weightedWidthScale = 0;
17352
+ let weightedLengthScale = 0;
17353
+ let weightedSwayScale = 0;
17354
+ let weightedDepthScale = 0;
17355
+ let weightedPhase = 0;
17356
+ for (const tentacleProfile of options.tentacleProfiles) {
17357
+ const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
17358
+ const width = 0.2 * tentacleProfile.widthScale;
17359
+ const weight = Math.exp(-(distance * distance) / (width * width));
17360
+ totalWeight += weight;
17361
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
17362
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
17363
+ weightedWidthScale += tentacleProfile.widthScale * weight;
17364
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
17365
+ weightedSwayScale += tentacleProfile.swayScale * weight;
17366
+ weightedDepthScale += tentacleProfile.depthScale * weight;
17367
+ weightedPhase += tentacleProfile.phase * weight;
17368
+ }
17369
+ if (totalWeight < 0.0001) {
17370
+ return {
17371
+ core: 0,
17372
+ centerLongitude: longitude,
17373
+ widthScale: 1,
17374
+ lengthScale: 1,
17375
+ swayScale: 1,
17376
+ depthScale: 1,
17377
+ phase: 0,
17378
+ };
17379
+ }
17380
+ return {
17381
+ core: clampNumber$1(totalWeight, 0, 1),
17382
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
17383
+ widthScale: weightedWidthScale / totalWeight,
17384
+ lengthScale: weightedLengthScale / totalWeight,
17385
+ swayScale: weightedSwayScale / totalWeight,
17386
+ depthScale: weightedDepthScale / totalWeight,
17387
+ phase: weightedPhase / totalWeight,
17388
+ };
17389
+ }
17390
+ /**
17391
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
17392
+ *
17393
+ * @private helper of `octopus3d3AvatarVisual`
17394
+ */
17395
+ function resolveContinuousLobeWave(options, longitude) {
17396
+ const { morphologyProfile, animationPhase, timeMs } = options;
17397
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
17398
+ animationPhase +
17399
+ timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
17400
+ 1) /
17401
+ 2);
17402
+ }
17403
+ /**
17404
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
17405
+ *
17406
+ * @private helper of `octopus3d3AvatarVisual`
17407
+ */
17408
+ function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
17409
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
17410
+ if (tonalProgress < 0.14) {
17411
+ return palette.highlight;
17412
+ }
17413
+ if (tonalProgress < 0.32) {
17414
+ return palette.secondary;
17415
+ }
17416
+ if (tonalProgress < 0.72) {
17417
+ return forwardness > 0.55 ? palette.secondary : palette.primary;
17418
+ }
17419
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
17420
+ }
17421
+ /**
17422
+ * Draws one projected mesh patch with soft shading and a subtle edge.
17423
+ *
17424
+ * @private helper of `octopus3d3AvatarVisual`
17425
+ */
17426
+ function drawContinuousSurfacePatch(context, surfacePatch) {
17427
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
17428
+ if (surfacePatch.lightIntensity > 0) {
17429
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
17430
+ }
17431
+ else if (surfacePatch.lightIntensity < 0) {
17432
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
17433
+ }
17434
+ context.save();
17435
+ context.beginPath();
17436
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
17437
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
17438
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
17439
+ }
17440
+ context.closePath();
17441
+ context.strokeStyle = surfacePatch.outlineColor;
17442
+ context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
17443
+ context.lineJoin = 'round';
17444
+ context.stroke();
17445
+ context.restore();
17446
+ }
17447
+ /**
17448
+ * Draws projected mantle-current lines on the front of the mesh.
17449
+ *
17450
+ * @private helper of `octopus3d3AvatarVisual`
17451
+ */
17452
+ function drawProjectedSurfaceCurrents(options) {
17453
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
17454
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
17455
+ const centerIndex = (currentCount - 1) / 2;
17456
+ context.save();
17457
+ context.lineCap = 'round';
17458
+ context.lineJoin = 'round';
17459
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
17460
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
17461
+ const projectedPoints = [];
17462
+ for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
17463
+ const progress = sampleIndex / 7;
17464
+ const latitude = -0.46 + progress * 0.74;
17465
+ const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
17466
+ const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
17467
+ if (scenePoint.z > center.z - size * 0.016) {
17468
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
17469
+ }
17470
+ }
17471
+ if (projectedPoints.length < 3) {
17472
+ continue;
17473
+ }
17474
+ context.beginPath();
17475
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
17476
+ for (const projectedPoint of projectedPoints.slice(1)) {
17477
+ context.lineTo(projectedPoint.x, projectedPoint.y);
17478
+ }
17479
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
17480
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
17481
+ context.stroke();
17482
+ }
17483
+ context.restore();
17484
+ }
17485
+ /**
17486
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
17487
+ *
17488
+ * @private helper of `octopus3d3AvatarVisual`
17489
+ */
17490
+ function drawProjectedTentacleSuckers(options) {
17491
+ const { surfaceOptions, size } = options;
17492
+ const { timeMs } = surfaceOptions;
17493
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
17494
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
17495
+ continue;
17496
+ }
17497
+ for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
17498
+ const latitude = 0.52 + suckerIndex * 0.14;
17499
+ const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
17500
+ const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
17501
+ drawProjectedSurfaceSpot({
17502
+ ...options,
17503
+ latitude,
17504
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
17505
+ radiusScale: size * (0.0065 - suckerIndex * 0.0007),
17506
+ });
17507
+ }
17508
+ }
17509
+ }
17510
+ /**
17511
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
17512
+ *
17513
+ * @private helper of `octopus3d3AvatarVisual`
17514
+ */
17515
+ function drawProjectedSurfaceSpot(options) {
17516
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
17517
+ const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
17518
+ const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
17519
+ const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
17520
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
17521
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
17522
+ return;
17523
+ }
17524
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
17525
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
17526
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
17527
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
17528
+ radiusScale *
17529
+ 0.74, size * 0.003, size * 0.018);
17530
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
17531
+ radiusScale *
17532
+ 0.52, size * 0.0024, size * 0.014);
17533
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
17534
+ context.save();
17535
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
17536
+ context.rotate(rotation);
17537
+ context.beginPath();
17538
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
17539
+ context.fillStyle = `${palette.highlight}73`;
17540
+ context.fill();
17541
+ context.strokeStyle = `${palette.highlight}99`;
17542
+ context.lineWidth = Math.max(0.7, size * 0.0028);
17543
+ context.stroke();
17544
+ context.restore();
17545
+ }
17546
+ /**
17547
+ * Resolves a signed angular distance from the source longitude to the target longitude.
17548
+ *
17549
+ * @private helper of `octopus3d3AvatarVisual`
17550
+ */
17551
+ function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
17552
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
17553
+ }
17554
+ /**
17555
+ * Smoothly maps a value between two bounds into `[0, 1]`.
17556
+ *
17557
+ * @private helper of `octopus3d3AvatarVisual`
17558
+ */
17559
+ function smoothStep$1(edgeStart, edgeEnd, value) {
17560
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
17561
+ return progress * progress * (3 - 2 * progress);
17562
+ }
17563
+
17564
+ /* eslint-disable no-magic-numbers */
17565
+ /**
17566
+ * Light direction used by the continuous Octopus 3D 4 mesh shading.
17567
+ *
17568
+ * @private helper of `octopus3d4AvatarVisual`
17569
+ */
17570
+ const LIGHT_DIRECTION = normalizeVector3({
17571
+ x: 0.32,
17572
+ y: -0.66,
17573
+ z: 1,
17574
+ });
17575
+ /**
17576
+ * Rim-light direction used to brighten the side of the silhouette.
17577
+ *
17578
+ * @private helper of `octopus3d4AvatarVisual`
17579
+ */
17580
+ const RIM_LIGHT_DIRECTION = normalizeVector3({
17581
+ x: -0.7,
17582
+ y: -0.18,
17583
+ z: 0.55,
17584
+ });
17585
+ /**
17586
+ * Real-octopus tentacle count used by the continuous lower mesh.
17587
+ *
17588
+ * @private helper of `octopus3d4AvatarVisual`
17589
+ */
17590
+ const OCTOPUS_TENTACLE_COUNT = 8;
17591
+ /**
17592
+ * Number of seeded skin spots painted across the octopus body.
17593
+ *
17594
+ * @private helper of `octopus3d4AvatarVisual`
17595
+ */
17596
+ const SKIN_SPOT_COUNT = 14;
17597
+ /**
17598
+ * Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
17599
+ *
17600
+ * @private helper of `octopus3d4AvatarVisual`
17601
+ */
17602
+ const octopus3d4StableStateCache = new WeakMap();
17603
+ /**
17604
+ * Returns the stable per-avatar state, computing it on first access and caching afterwards.
17605
+ *
17606
+ * @private helper of `octopus3d4AvatarVisual`
17607
+ */
17608
+ function getOctopus3d4StableState(createRandom) {
17609
+ const cached = octopus3d4StableStateCache.get(createRandom);
17610
+ if (cached !== undefined) {
17611
+ return cached;
17612
+ }
17613
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
17614
+ const animationRandom = createRandom('octopus3d4-animation-profile');
17615
+ const eyeRandom = createRandom('octopus3d4-eye-profile');
17616
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
17617
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
17618
+ const state = {
17619
+ morphologyProfile,
17620
+ animationPhase: animationRandom() * Math.PI * 2,
17621
+ leftEyePhaseOffset,
17622
+ rightEyePhaseOffset,
17623
+ tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
17624
+ skinSpots: createBlobbySkinSpots(createRandom),
17625
+ };
17626
+ octopus3d4StableStateCache.set(createRandom, state);
17627
+ return state;
17628
+ }
17629
+ /**
17630
+ * Octopus 3D 4 avatar visual.
17631
+ *
17632
+ * @private built-in avatar visual
17633
+ */
17634
+ const octopus3d4AvatarVisual = {
17635
+ id: 'octopus3d4',
17636
+ title: 'Octopus 3D 4',
17637
+ 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.',
17638
+ isAnimated: true,
17639
+ supportsPointerTracking: true,
17640
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
17641
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
17642
+ const sceneCenterX = size * 0.5;
17643
+ const sceneCenterY = size * 0.535;
17644
+ const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
17645
+ const meshCenter = {
17646
+ x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
17647
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
17648
+ z: interaction.intensity * size * 0.02,
17649
+ };
17650
+ const rotationY = -0.08 +
17651
+ Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
17652
+ interaction.bodyOffsetX * 0.24 +
17653
+ interaction.gazeX * 0.98;
17654
+ const rotationX = -0.07 +
17655
+ Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
17656
+ interaction.bodyOffsetY * 0.08 -
17657
+ interaction.gazeY * 0.42;
17658
+ const surfaceOptions = {
17659
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
17660
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
17661
+ radiusZ: size *
17662
+ morphologyProfile.body.bodyRadiusRatio *
17663
+ (1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
17664
+ morphologyProfile,
17665
+ timeMs,
17666
+ animationPhase,
17667
+ tentacleProfiles,
17668
+ };
17669
+ const surfacePatches = resolveVisibleBlobbyContinuousPatches({
17670
+ ...surfaceOptions,
17671
+ center: meshCenter,
17672
+ rotationX,
17673
+ rotationY,
17674
+ sceneCenterX,
17675
+ sceneCenterY,
17676
+ size,
17677
+ palette,
17678
+ });
17679
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
17680
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
17681
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
17682
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
17683
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
17684
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
17685
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
17686
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
17687
+ drawAvatarFrame(context, size, palette);
17688
+ drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
17689
+ drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
17690
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
17691
+ drawBlobbyContinuousSurfacePatch(context, surfacePatch);
17692
+ }
17693
+ drawBlobbySkinSpots({
17694
+ context,
17695
+ surfaceOptions,
17696
+ center: meshCenter,
17697
+ rotationX,
17698
+ rotationY,
17699
+ sceneCenterX,
17700
+ sceneCenterY,
17701
+ size,
17702
+ palette,
17703
+ skinSpots,
17704
+ });
17705
+ drawBlobbyContinuousCurrents({
17706
+ context,
17707
+ surfaceOptions,
17708
+ center: meshCenter,
17709
+ rotationX,
17710
+ rotationY,
17711
+ sceneCenterX,
17712
+ sceneCenterY,
17713
+ size,
17714
+ palette,
17715
+ morphologyProfile,
17716
+ timeMs,
17717
+ animationPhase,
17718
+ });
17719
+ drawBlobbyContinuousSuckers({
17720
+ context,
17721
+ surfaceOptions,
17722
+ center: meshCenter,
17723
+ rotationX,
17724
+ rotationY,
17725
+ sceneCenterX,
17726
+ sceneCenterY,
17727
+ size,
17728
+ palette,
17729
+ });
17730
+ drawBlobbyContinuousGloss({
17731
+ context,
17732
+ surfaceOptions,
17733
+ center: meshCenter,
17734
+ rotationX,
17735
+ rotationY,
17736
+ sceneCenterX,
17737
+ sceneCenterY,
17738
+ size,
17739
+ palette,
17740
+ });
17741
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17742
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17743
+ drawProjectedOrganicMouth(context, [
17744
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
17745
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
17746
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
17747
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
17748
+ },
17749
+ };
17750
+ /**
17751
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
17752
+ *
17753
+ * @private helper of `octopus3d4AvatarVisual`
17754
+ */
17755
+ function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
17756
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
17757
+ const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
17758
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
17759
+ return {
17760
+ centerLongitude: -Math.PI * 0.9 +
17761
+ progress * Math.PI * 1.8 +
17762
+ (tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
17763
+ widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
17764
+ lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
17765
+ swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
17766
+ depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
17767
+ curlScale: 0.62 + tentacleRandom() * 0.46,
17768
+ primaryPhase: tentacleRandom() * Math.PI * 2,
17769
+ secondaryPhase: tentacleRandom() * Math.PI * 2,
17770
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
17771
+ };
17772
+ });
17773
+ }
17774
+ /**
17775
+ * Creates seeded skin pigment spots distributed across the upper octopus mesh.
17776
+ *
17777
+ * @private helper of `octopus3d4AvatarVisual`
17778
+ */
17779
+ function createBlobbySkinSpots(createRandom) {
17780
+ const spotRandom = createRandom('octopus3d4-skin-spots');
17781
+ return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
17782
+ latitude: -0.45 + spotRandom() * 0.7,
17783
+ longitude: -0.5 + spotRandom() * 1.0,
17784
+ radiusScale: 0.0028 + spotRandom() * 0.0052,
17785
+ opacity: 0.16 + spotRandom() * 0.22,
17786
+ }));
17787
+ }
17788
+ /**
17789
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
17790
+ *
17791
+ * @private helper of `octopus3d4AvatarVisual`
17792
+ */
17793
+ function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
17794
+ 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));
17795
+ glowGradient.addColorStop(0, `${palette.highlight}74`);
17796
+ glowGradient.addColorStop(0.32, `${palette.accent}32`);
17797
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
17798
+ context.fillStyle = glowGradient;
17799
+ context.fillRect(0, 0, size, size);
17800
+ 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);
17801
+ lowerGradient.addColorStop(0, `${palette.secondary}28`);
17802
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
17803
+ context.fillStyle = lowerGradient;
17804
+ context.fillRect(0, 0, size, size);
17805
+ }
17806
+ /**
17807
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
17808
+ *
17809
+ * @private helper of `octopus3d4AvatarVisual`
17810
+ */
17811
+ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
17812
+ const cx = size * 0.5 + interaction.gazeX * size * 0.046;
17813
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
17814
+ const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
17815
+ const ry = size * 0.062;
17816
+ context.save();
17817
+ context.translate(cx, cy);
17818
+ context.scale(1, ry / rx);
17819
+ const blurRadius = rx * 1.42;
17820
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
17821
+ shadowGradient.addColorStop(0, `${palette.shadow}82`);
17822
+ shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
17823
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
17824
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
17825
+ context.fillStyle = shadowGradient;
17826
+ context.beginPath();
17827
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
17828
+ context.fill();
17829
+ context.restore();
17830
+ }
17831
+ /**
17832
+ * Number of latitude segments used by the continuous Octopus 3D 4 mesh.
17833
+ *
17834
+ * @private helper of `octopus3d4AvatarVisual`
17835
+ */
17836
+ const LATITUDE_PATCH_COUNT = 20;
17837
+ /**
17838
+ * Number of longitude segments used by the continuous Octopus 3D 4 mesh.
17839
+ *
17840
+ * @private helper of `octopus3d4AvatarVisual`
17841
+ */
17842
+ const LONGITUDE_PATCH_COUNT = 48;
17843
+ /**
17844
+ * Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
17845
+ *
17846
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
17847
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
17848
+ * than re-evaluated for every patch corner.
17849
+ *
17850
+ * @private helper of `octopus3d4AvatarVisual`
17851
+ */
17852
+ function resolveVisibleBlobbyContinuousPatches(options) {
17853
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
17854
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
17855
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
17856
+ const surfacePatches = [];
17857
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
17858
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
17859
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
17860
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
17861
+ }
17862
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17863
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
17864
+ }
17865
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
17866
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
17867
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
17868
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17869
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17870
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17871
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
17872
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
17873
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
17874
+ }
17875
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17876
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
17877
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
17878
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
17879
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
17880
+ }
17881
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
17882
+ const transformedCornerSamples = new Array(cornerCount);
17883
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
17884
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
17885
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
17886
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
17887
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
17888
+ const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
17889
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
17890
+ }
17891
+ }
17892
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
17893
+ const startLatitude = latitudeBoundaries[latitudeIndex];
17894
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
17895
+ const centerLatitude = (startLatitude + endLatitude) / 2;
17896
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
17897
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
17898
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
17899
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17900
+ const transformedCorners = [
17901
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
17902
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
17903
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
17904
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
17905
+ ];
17906
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
17907
+ if (surfaceNormal.z <= 0.006) {
17908
+ continue;
17909
+ }
17910
+ const projectedCorners = [
17911
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
17912
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
17913
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
17914
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
17915
+ ];
17916
+ const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
17917
+ const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
17918
+ surfacePatches.push({
17919
+ corners: projectedCorners,
17920
+ averageDepth: (transformedCorners[0].z +
17921
+ transformedCorners[1].z +
17922
+ transformedCorners[2].z +
17923
+ transformedCorners[3].z) /
17924
+ 4,
17925
+ lightIntensity,
17926
+ rimLightIntensity,
17927
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
17928
+ outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
17929
+ });
17930
+ }
17931
+ }
17932
+ return surfacePatches;
17933
+ }
17934
+ /**
17935
+ * Samples one point on the continuous Octopus 3D 4 surface.
17936
+ *
17937
+ * @private helper of `octopus3d4AvatarVisual`
17938
+ */
17939
+ function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
17940
+ return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
17941
+ }
17942
+ /**
17943
+ * Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
17944
+ *
17945
+ * The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
17946
+ * across every latitude row and each tentacle/lobe value is computed once per frame instead
17947
+ * of `latitudePatchCount * 4` times.
17948
+ *
17949
+ * @private helper of `octopus3d4AvatarVisual`
17950
+ */
17951
+ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
17952
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
17953
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
17954
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
17955
+ const upperBlend = Math.pow(1 - verticalProgress, 1.32);
17956
+ const lowerBlend = smoothStep(0.34, 1, verticalProgress);
17957
+ const tipBlend = smoothStep(0.66, 1, verticalProgress);
17958
+ const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
17959
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
17960
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
17961
+ animationPhase * 0.6 +
17962
+ timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
17963
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
17964
+ (0.32 + lowerBlend * 0.7);
17965
+ const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
17966
+ lowerBlend *
17967
+ tentacleInfluence.core *
17968
+ tentacleInfluence.swayScale;
17969
+ const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
17970
+ lowerBlend *
17971
+ tentacleInfluence.core *
17972
+ tentacleInfluence.swayScale *
17973
+ 0.42;
17974
+ const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
17975
+ tipBlend *
17976
+ tentacleInfluence.core *
17977
+ tentacleInfluence.curlScale *
17978
+ 0.36;
17979
+ const horizontalScale = 1.06 +
17980
+ mantleRipple +
17981
+ lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
17982
+ lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
17983
+ upperBlend * 0.08;
17984
+ const depthScale = 1.08 +
17985
+ upperBlend * 0.16 +
17986
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
17987
+ lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
17988
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
17989
+ const tentacleTubeRadius = lowerBlend *
17990
+ tentacleInfluence.core *
17991
+ (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
17992
+ radiusX;
17993
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
17994
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
17995
+ const lowerDrop = lowerBlend *
17996
+ radiusY *
17997
+ (0.2 +
17998
+ tentacleInfluence.core *
17999
+ (0.42 +
18000
+ tentacleInfluence.lengthScale * 0.24 +
18001
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
18002
+ const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
18003
+ return {
18004
+ x: Math.sin(effectiveLongitude) * planarRadiusX +
18005
+ combinedTentacleSway +
18006
+ tentacleCurl * radiusX * 0.18,
18007
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
18008
+ upperBlend * radiusY * 0.12 +
18009
+ lowerDrop +
18010
+ Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
18011
+ Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
18012
+ lowerBlend *
18013
+ tipBlend *
18014
+ tentacleInfluence.core *
18015
+ radiusY *
18016
+ 0.04,
18017
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
18018
+ Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
18019
+ lowerBlend *
18020
+ tentacleInfluence.core *
18021
+ radiusZ *
18022
+ 0.044 +
18023
+ tentacleCurl * radiusZ * 0.14,
18024
+ };
18025
+ }
18026
+ /**
18027
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
18028
+ *
18029
+ * @private helper of `octopus3d4AvatarVisual`
18030
+ */
18031
+ function resolveBlobbyTentacleInfluence(options, longitude) {
18032
+ let totalWeight = 0;
18033
+ let weightedSin = 0;
18034
+ let weightedCos = 0;
18035
+ let weightedWidthScale = 0;
18036
+ let weightedLengthScale = 0;
18037
+ let weightedSwayScale = 0;
18038
+ let weightedDepthScale = 0;
18039
+ let weightedCurlScale = 0;
18040
+ let weightedPrimaryPhase = 0;
18041
+ let weightedSecondaryPhase = 0;
18042
+ for (const tentacleProfile of options.tentacleProfiles) {
18043
+ const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
18044
+ const width = 0.22 * tentacleProfile.widthScale;
18045
+ const weight = Math.exp(-(distance * distance) / (width * width));
18046
+ totalWeight += weight;
18047
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
18048
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
18049
+ weightedWidthScale += tentacleProfile.widthScale * weight;
18050
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
18051
+ weightedSwayScale += tentacleProfile.swayScale * weight;
18052
+ weightedDepthScale += tentacleProfile.depthScale * weight;
18053
+ weightedCurlScale += tentacleProfile.curlScale * weight;
18054
+ weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
18055
+ weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
18056
+ }
18057
+ if (totalWeight < 0.0001) {
18058
+ return {
18059
+ core: 0,
18060
+ centerLongitude: longitude,
18061
+ widthScale: 1,
18062
+ lengthScale: 1,
18063
+ swayScale: 1,
18064
+ depthScale: 1,
18065
+ curlScale: 1,
18066
+ primaryPhase: 0,
18067
+ secondaryPhase: 0,
18068
+ };
18069
+ }
18070
+ return {
18071
+ core: clampNumber$1(totalWeight, 0, 1),
18072
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
18073
+ widthScale: weightedWidthScale / totalWeight,
18074
+ lengthScale: weightedLengthScale / totalWeight,
18075
+ swayScale: weightedSwayScale / totalWeight,
18076
+ depthScale: weightedDepthScale / totalWeight,
18077
+ curlScale: weightedCurlScale / totalWeight,
18078
+ primaryPhase: weightedPrimaryPhase / totalWeight,
18079
+ secondaryPhase: weightedSecondaryPhase / totalWeight,
18080
+ };
18081
+ }
18082
+ /**
18083
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
18084
+ *
18085
+ * @private helper of `octopus3d4AvatarVisual`
18086
+ */
18087
+ function resolveBlobbyLobeWave(options, longitude) {
18088
+ const { morphologyProfile, animationPhase, timeMs } = options;
18089
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
18090
+ animationPhase +
18091
+ timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
18092
+ 1) /
18093
+ 2);
18094
+ }
18095
+ /**
18096
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
18097
+ *
18098
+ * @private helper of `octopus3d4AvatarVisual`
18099
+ */
18100
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
18101
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
18102
+ if (tonalProgress < 0.12) {
18103
+ return palette.highlight;
18104
+ }
18105
+ if (tonalProgress < 0.3) {
18106
+ return palette.secondary;
18107
+ }
18108
+ if (tonalProgress < 0.72) {
18109
+ return forwardness > 0.56 ? palette.secondary : palette.primary;
18110
+ }
18111
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
18112
+ }
18113
+ /**
18114
+ * Draws one projected mesh patch with multi-pass shading and a soft edge.
18115
+ *
18116
+ * @private helper of `octopus3d4AvatarVisual`
18117
+ */
18118
+ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
18119
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
18120
+ if (surfacePatch.lightIntensity > 0) {
18121
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
18122
+ }
18123
+ else if (surfacePatch.lightIntensity < 0) {
18124
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
18125
+ }
18126
+ if (surfacePatch.rimLightIntensity > 0.04) {
18127
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
18128
+ }
18129
+ context.save();
18130
+ context.beginPath();
18131
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
18132
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
18133
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
18134
+ }
18135
+ context.closePath();
18136
+ context.strokeStyle = surfacePatch.outlineColor;
18137
+ context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
18138
+ context.lineJoin = 'round';
18139
+ context.stroke();
18140
+ context.restore();
18141
+ }
18142
+ /**
18143
+ * Draws projected mantle-current lines on the front of the mesh.
18144
+ *
18145
+ * @private helper of `octopus3d4AvatarVisual`
18146
+ */
18147
+ function drawBlobbyContinuousCurrents(options) {
18148
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
18149
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
18150
+ const centerIndex = (currentCount - 1) / 2;
18151
+ context.save();
18152
+ context.lineCap = 'round';
18153
+ context.lineJoin = 'round';
18154
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
18155
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
18156
+ const projectedPoints = [];
18157
+ for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
18158
+ const progress = sampleIndex / 8;
18159
+ const latitude = -0.48 + progress * 0.78;
18160
+ const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
18161
+ const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
18162
+ if (scenePoint.z > center.z - size * 0.016) {
18163
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
18164
+ }
18165
+ }
18166
+ if (projectedPoints.length < 3) {
18167
+ continue;
18168
+ }
18169
+ context.beginPath();
18170
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
18171
+ for (const projectedPoint of projectedPoints.slice(1)) {
18172
+ context.lineTo(projectedPoint.x, projectedPoint.y);
18173
+ }
18174
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
18175
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
18176
+ context.stroke();
18177
+ }
18178
+ context.restore();
18179
+ }
18180
+ /**
18181
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
18182
+ *
18183
+ * @private helper of `octopus3d4AvatarVisual`
18184
+ */
18185
+ function drawBlobbyContinuousSuckers(options) {
18186
+ const { surfaceOptions, size } = options;
18187
+ const { timeMs } = surfaceOptions;
18188
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
18189
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
18190
+ continue;
18191
+ }
18192
+ for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
18193
+ const latitude = 0.5 + suckerIndex * 0.12;
18194
+ const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
18195
+ const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
18196
+ drawBlobbyContinuousSurfaceSpot({
18197
+ ...options,
18198
+ latitude,
18199
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
18200
+ radiusScale: size * (0.0068 - suckerIndex * 0.0006),
18201
+ });
18202
+ }
18203
+ }
18204
+ }
18205
+ /**
18206
+ * Draws seeded pigment spots across the upper mesh for a richer skin texture.
18207
+ *
18208
+ * @private helper of `octopus3d4AvatarVisual`
18209
+ */
18210
+ function drawBlobbySkinSpots(options) {
18211
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
18212
+ for (const skinSpot of skinSpots) {
18213
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
18214
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18215
+ if (sceneCenterPoint.z <= center.z - size * 0.01) {
18216
+ continue;
18217
+ }
18218
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
18219
+ const spotRadius = size * skinSpot.radiusScale;
18220
+ context.save();
18221
+ context.beginPath();
18222
+ context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
18223
+ context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
18224
+ context.fill();
18225
+ context.restore();
18226
+ }
18227
+ }
18228
+ /**
18229
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
18230
+ *
18231
+ * @private helper of `octopus3d4AvatarVisual`
18232
+ */
18233
+ function drawBlobbyContinuousSurfaceSpot(options) {
18234
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
18235
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
18236
+ const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
18237
+ const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
18238
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18239
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
18240
+ return;
18241
+ }
18242
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
18243
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
18244
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
18245
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
18246
+ radiusScale *
18247
+ 0.78, size * 0.003, size * 0.02);
18248
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
18249
+ radiusScale *
18250
+ 0.54, size * 0.0024, size * 0.015);
18251
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
18252
+ context.save();
18253
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
18254
+ context.rotate(rotation);
18255
+ context.beginPath();
18256
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
18257
+ context.fillStyle = `${palette.highlight}80`;
18258
+ context.fill();
18259
+ context.strokeStyle = `${palette.highlight}a8`;
18260
+ context.lineWidth = Math.max(0.7, size * 0.0028);
18261
+ context.stroke();
18262
+ context.restore();
18263
+ }
18264
+ /**
18265
+ * Draws a soft, slowly drifting gloss highlight on the front of the mesh.
18266
+ *
18267
+ * @private helper of `octopus3d4AvatarVisual`
18268
+ */
18269
+ function drawBlobbyContinuousGloss(options) {
18270
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
18271
+ const { timeMs, animationPhase } = surfaceOptions;
18272
+ const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
18273
+ const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
18274
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
18275
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18276
+ if (sceneCenterPoint.z <= center.z) {
18277
+ return;
18278
+ }
18279
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
18280
+ const glossRadius = size * 0.058;
18281
+ context.save();
18282
+ const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
18283
+ glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
18284
+ glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
18285
+ glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
18286
+ context.fillStyle = glossGradient;
18287
+ context.beginPath();
18288
+ context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
18289
+ context.fill();
18290
+ context.restore();
18291
+ }
18292
+ /**
18293
+ * Resolves a signed angular distance from the source longitude to the target longitude.
18294
+ *
18295
+ * @private helper of `octopus3d4AvatarVisual`
18296
+ */
18297
+ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
18298
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
18299
+ }
18300
+ /**
18301
+ * Smoothly maps a value between two bounds into `[0, 1]`.
18302
+ *
18303
+ * @private helper of `octopus3d4AvatarVisual`
18304
+ */
18305
+ function smoothStep(edgeStart, edgeEnd, value) {
18306
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
18307
+ return progress * progress * (3 - 2 * progress);
18308
+ }
18309
+ /**
18310
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
18311
+ *
18312
+ * @private helper of `octopus3d4AvatarVisual`
18313
+ */
18314
+ function formatAlphaHex(opacity) {
18315
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
18316
+ .toString(16)
18317
+ .padStart(2, '0');
18318
+ }
18319
+
16770
18320
  /* eslint-disable no-magic-numbers */
16771
18321
  /**
16772
18322
  * Octopus avatar visual.
@@ -17537,6 +19087,8 @@ const AVATAR_VISUALS = [
17537
19087
  octopus3AvatarVisual,
17538
19088
  octopus3dAvatarVisual,
17539
19089
  octopus3d2AvatarVisual,
19090
+ octopus3d3AvatarVisual,
19091
+ octopus3d4AvatarVisual,
17540
19092
  asciiOctopusAvatarVisual,
17541
19093
  minecraftAvatarVisual,
17542
19094
  minecraft2AvatarVisual,
@@ -19825,7 +21377,15 @@ function buildTeammateMetadata(entry) {
19825
21377
  * Builds the teammate request text, optionally including context.
19826
21378
  */
19827
21379
  function buildTeammateRequest(message, context) {
19828
- return context ? `${message}\n\nContext:\n${context}` : message;
21380
+ if (!context) {
21381
+ return message;
21382
+ }
21383
+ return spaceTrim$1((block) => `
21384
+ ${block(message)}
21385
+
21386
+ Context:
21387
+ ${block(context)}
21388
+ `);
19829
21389
  }
19830
21390
  /**
19831
21391
  * Builds a minimal chat prompt for teammate calls.
@@ -23814,7 +25374,11 @@ function createUseProjectToolFunctions() {
23814
25374
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
23815
25375
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
23816
25376
  const contentToReturn = wasCharacterTruncated
23817
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
25377
+ ? spaceTrim$1((block) => `
25378
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
25379
+
25380
+ [...truncated...]
25381
+ `)
23818
25382
  : lineRangedContent;
23819
25383
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
23820
25384
  if (wasCharacterTruncated) {
@@ -32749,11 +34313,19 @@ const JsonFileImportPlugin = {
32749
34313
  try {
32750
34314
  const json = JSON.parse(content);
32751
34315
  const formattedJson = JSON.stringify(json, null, 4);
32752
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
34316
+ return spaceTrim$1((block) => `
34317
+ \`\`\`json
34318
+ ${block(formattedJson)}
34319
+ \`\`\`
34320
+ `);
32753
34321
  }
32754
34322
  catch (error) {
32755
34323
  // If JSON is invalid, still import it but maybe not as pretty JSON
32756
- return `\`\`\`json\n${content}\n\`\`\``;
34324
+ return spaceTrim$1((block) => `
34325
+ \`\`\`json
34326
+ ${block(content)}
34327
+ \`\`\`
34328
+ `);
32757
34329
  }
32758
34330
  },
32759
34331
  };
@@ -32777,7 +34349,11 @@ const TextFileImportPlugin = {
32777
34349
  import(content, mimeType) {
32778
34350
  const extension = mimeTypeToExtension(mimeType);
32779
34351
  const codeBlockType = extension || 'txt';
32780
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
34352
+ return spaceTrim$1((block) => `
34353
+ \`\`\`${codeBlockType}
34354
+ ${block(content)}
34355
+ \`\`\`
34356
+ `);
32781
34357
  },
32782
34358
  };
32783
34359
 
@@ -33024,11 +34600,19 @@ function collectExampleInteractionLines(parseResult, samples) {
33024
34600
  const examples = [];
33025
34601
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
33026
34602
  if (initialMessage) {
33027
- examples.push(`**Agent:**\n${initialMessage}`);
34603
+ examples.push(spaceTrim$1((block) => `
34604
+ **Agent:**
34605
+ ${block(initialMessage)}
34606
+ `));
33028
34607
  }
33029
34608
  if (samples && samples.length > 0) {
33030
34609
  for (const sample of samples) {
33031
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
34610
+ examples.push(spaceTrim$1((block) => `
34611
+ **User:** ${block(String(sample.question))}
34612
+
34613
+ **Agent:**
34614
+ ${block(sample.answer)}
34615
+ `));
33032
34616
  }
33033
34617
  }
33034
34618
  return examples;
@@ -34350,7 +35934,9 @@ class OpenAiAgentKitExecutionToolsToolBuilder {
34350
35934
  if (!executionTools || !executionTools.script) {
34351
35935
  throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
34352
35936
  }
34353
- return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
35937
+ return Array.isArray(executionTools.script)
35938
+ ? executionTools.script
35939
+ : [executionTools.script];
34354
35940
  }
34355
35941
  /**
34356
35942
  * Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
@@ -37412,7 +38998,7 @@ class OpenAiVectorStoreKnowledgeSourcePreparer {
37412
38998
  return { skippedReason: 'invalid_data_url' };
37413
38999
  }
37414
39000
  return {
37415
- file: new File([parsed.buffer], parsed.filename, {
39001
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
37416
39002
  type: parsed.mimeType,
37417
39003
  }),
37418
39004
  sizeBytes: parsed.buffer.length,
@@ -39160,7 +40746,7 @@ function emitAgentLlmExecutionToolsAssistantPreparationProgress(options) {
39160
40746
  * Computes one stable hash from a JSON-serializable value.
39161
40747
  */
39162
40748
  function computeJsonHash$1(value) {
39163
- return SHA256(JSON.stringify(value)).toString();
40749
+ return CryptoJS.SHA256(JSON.stringify(value)).toString();
39164
40750
  }
39165
40751
  /**
39166
40752
  * Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
@@ -39318,7 +40904,7 @@ AgentLlmExecutionToolsAgentKitRunner.vectorStoreCache = new Map();
39318
40904
  * Computes one stable hash from a JSON-serializable value.
39319
40905
  */
39320
40906
  function computeJsonHash(value) {
39321
- return SHA256(JSON.stringify(value)).toString();
40907
+ return CryptoJS.SHA256(JSON.stringify(value)).toString();
39322
40908
  }
39323
40909
  /**
39324
40910
  * Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
@@ -39654,7 +41240,11 @@ function truncateAttachmentInlineText(content, maxCharacters) {
39654
41240
  };
39655
41241
  }
39656
41242
  return {
39657
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
41243
+ content: spaceTrim$1((block) => `
41244
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
41245
+
41246
+ [...truncated...]
41247
+ `),
39658
41248
  isTruncated: true,
39659
41249
  };
39660
41250
  }
@@ -40120,7 +41710,7 @@ class AgentLlmExecutionTools {
40120
41710
  * Returns a virtual model name representing the agent behavior.
40121
41711
  */
40122
41712
  get modelName() {
40123
- const hash = SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
41713
+ const hash = CryptoJS.SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
40124
41714
  const agentId = hash.substring(0, 10);
40125
41715
  return (normalizeToKebabCase(this.title) + '-' + agentId);
40126
41716
  }