@promptbook/core 0.112.0-99 → 0.113.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +76 -44
  2. package/esm/index.es.js +2064 -158
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/servers.d.ts +1 -9
  5. package/esm/src/_packages/components.index.d.ts +4 -0
  6. package/esm/src/_packages/core.index.d.ts +22 -2
  7. package/esm/src/_packages/node.index.d.ts +40 -0
  8. package/esm/src/_packages/types.index.d.ts +20 -0
  9. package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  10. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  11. package/esm/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
  12. package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  13. package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  14. package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  15. package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  16. package/esm/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
  17. package/esm/src/book-3.0/CliAgent.d.ts +66 -0
  18. package/esm/src/book-3.0/CliAgent.test.d.ts +1 -0
  19. package/esm/src/book-3.0/LiteAgent.d.ts +68 -0
  20. package/esm/src/book-3.0/LiteAgent.test.d.ts +1 -0
  21. package/esm/src/book-3.0/agentFolderPaths.d.ts +30 -0
  22. package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
  23. package/esm/src/book-components/BookEditor/BookEditor.d.ts +6 -5
  24. package/esm/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
  25. package/esm/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
  26. package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
  27. package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
  28. package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  29. package/esm/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
  30. package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  31. package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  32. package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  33. package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  34. package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  35. package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  36. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  37. package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  38. package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  39. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  40. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  41. package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  42. package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  43. package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  44. package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  45. package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  46. package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  47. package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  48. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  49. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  50. package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
  51. package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  52. package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  53. package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  54. package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
  55. package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
  56. package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
  57. package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  58. package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  59. package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
  60. package/esm/src/cli/cli-commands/agent/chat.d.ts +10 -0
  61. package/esm/src/cli/cli-commands/agent/exec.d.ts +10 -0
  62. package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  63. package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
  64. package/esm/src/cli/cli-commands/agent.d.ts +14 -0
  65. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
  66. package/esm/src/cli/cli-commands/agents-server/run.d.ts +6 -0
  67. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
  68. package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  69. package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
  70. package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  71. package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
  72. package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
  73. package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
  74. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
  75. package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  76. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
  77. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  78. package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  79. package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
  80. package/esm/src/commitments/index.d.ts +2 -1
  81. package/esm/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
  82. package/esm/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
  83. package/esm/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
  84. package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -0
  85. package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  86. package/esm/src/version.d.ts +1 -1
  87. package/package.json +1 -1
  88. package/umd/index.umd.js +2075 -158
  89. package/umd/index.umd.js.map +1 -1
  90. package/umd/servers.d.ts +1 -9
  91. package/umd/src/_packages/components.index.d.ts +4 -0
  92. package/umd/src/_packages/core.index.d.ts +22 -2
  93. package/umd/src/_packages/node.index.d.ts +40 -0
  94. package/umd/src/_packages/types.index.d.ts +20 -0
  95. package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  96. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  97. package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
  98. package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  99. package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  100. package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  101. package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  102. package/umd/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
  103. package/umd/src/book-3.0/CliAgent.d.ts +66 -0
  104. package/umd/src/book-3.0/CliAgent.test.d.ts +1 -0
  105. package/umd/src/book-3.0/LiteAgent.d.ts +68 -0
  106. package/umd/src/book-3.0/LiteAgent.test.d.ts +1 -0
  107. package/umd/src/book-3.0/agentFolderPaths.d.ts +30 -0
  108. package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
  109. package/umd/src/book-components/BookEditor/BookEditor.d.ts +6 -5
  110. package/umd/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
  111. package/umd/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
  112. package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
  113. package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
  114. package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  115. package/umd/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
  116. package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  117. package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  118. package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  119. package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  120. package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  121. package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  122. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  123. package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  124. package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  125. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  126. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  127. package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  128. package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  129. package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  130. package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  131. package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  132. package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  133. package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  134. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  135. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  136. package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
  137. package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  138. package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  139. package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  140. package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
  141. package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
  142. package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
  143. package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  144. package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  145. package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
  146. package/umd/src/cli/cli-commands/agent/chat.d.ts +10 -0
  147. package/umd/src/cli/cli-commands/agent/exec.d.ts +10 -0
  148. package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  149. package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
  150. package/umd/src/cli/cli-commands/agent.d.ts +14 -0
  151. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
  152. package/umd/src/cli/cli-commands/agents-server/run.d.ts +6 -0
  153. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
  154. package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  155. package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
  156. package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  157. package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
  158. package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
  159. package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
  160. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
  161. package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  162. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
  163. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  164. package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  165. package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
  166. package/umd/src/commitments/index.d.ts +2 -1
  167. package/umd/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
  168. package/umd/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
  169. package/umd/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
  170. package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -0
  171. package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  172. package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js CHANGED
@@ -1,13 +1,15 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('crypto-js'), require('crypto-js/enc-hex'), require('spacetrim'), require('crypto'), require('rxjs'), require('waitasecond'), require('crypto-js/sha256'), require('path'), require('mime-types'), require('papaparse'), require('moment'), require('colors'), require('@openai/agents'), require('bottleneck'), require('openai')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'crypto-js', 'crypto-js/enc-hex', 'spacetrim', 'crypto', 'rxjs', 'waitasecond', 'crypto-js/sha256', 'path', 'mime-types', 'papaparse', 'moment', 'colors', '@openai/agents', 'bottleneck', 'openai'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-core"] = {}, global.cryptoJs, global.hexEncoder, global.spacetrim, global.crypto, global.rxjs, global.waitasecond, global.sha256, global.path, global.mimeTypes, global.papaparse, global.moment, global.colors, global.agents, global.Bottleneck, global.OpenAI));
5
- })(this, (function (exports, cryptoJs, hexEncoder, spacetrim, crypto, rxjs, waitasecond, sha256, path, mimeTypes, papaparse, moment, colors, agents, Bottleneck, OpenAI) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('spacetrim'), require('crypto-js'), require('crypto-js/enc-hex'), require('rxjs'), require('waitasecond'), require('crypto-js/sha256'), require('path'), require('mime-types'), require('papaparse'), require('moment'), require('crypto'), require('colors'), require('@openai/agents'), require('bottleneck'), require('openai')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'spacetrim', 'crypto-js', 'crypto-js/enc-hex', 'rxjs', 'waitasecond', 'crypto-js/sha256', 'path', 'mime-types', 'papaparse', 'moment', 'crypto', 'colors', '@openai/agents', 'bottleneck', 'openai'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-core"] = {}, global.spacetrim, global.CryptoJS, global.hexEncoder, global.rxjs, global.waitasecond, global.sha256, global.path, global.mimeTypes, global.papaparse, global.moment, global.crypto, global.colors, global.agents, global.Bottleneck, global.OpenAI));
5
+ })(this, (function (exports, spacetrim, CryptoJS, hexEncoder, rxjs, waitasecond, sha256, path, mimeTypes, papaparse, moment, crypto, colors, agents, Bottleneck, OpenAI) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
9
+ var CryptoJS__default = /*#__PURE__*/_interopDefaultLegacy(CryptoJS);
9
10
  var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
10
11
  var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
12
+ var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
11
13
  var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
12
14
  var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
13
15
  var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
@@ -27,12 +29,26 @@
27
29
  * @generated
28
30
  * @see https://github.com/webgptorg/promptbook
29
31
  */
30
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-99';
32
+ const PROMPTBOOK_ENGINE_VERSION = '0.113.0-0';
31
33
  /**
32
34
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
33
35
  * Note: [💞] Ignore a discrepancy between file name and entity name
34
36
  */
35
37
 
38
+ /**
39
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
40
+ *
41
+ * @public exported from `@promptbook/core`
42
+ */
43
+ class ParseError extends Error {
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = 'ParseError';
47
+ Object.setPrototypeOf(this, ParseError.prototype);
48
+ }
49
+ }
50
+ // TODO: Maybe split `ParseError` and `ApplyError`
51
+
36
52
  /**
37
53
  * Trims string from all 4 sides
38
54
  *
@@ -45,6 +61,181 @@
45
61
  */
46
62
  const spaceTrim = spacetrim.spaceTrim;
47
63
 
64
+ /**
65
+ * Supported visibility states for persisted agents.
66
+ *
67
+ * @public exported from `@promptbook/core`
68
+ */
69
+ const AGENT_VISIBILITY_VALUES = ['PRIVATE', 'UNLISTED', 'PUBLIC'];
70
+ /**
71
+ * Fallback visibility used when no valid value is configured.
72
+ *
73
+ * @public exported from `@promptbook/core`
74
+ */
75
+ const DEFAULT_AGENT_VISIBILITY = 'UNLISTED';
76
+ /**
77
+ * Matches a single-line `META VISIBILITY` commitment in book source.
78
+ */
79
+ const META_VISIBILITY_LINE_PATTERN = /^(\s*)META\s+VISIBILITY\b([\s\S]*)$/iu;
80
+ /**
81
+ * Returns `true` when the value is one of supported visibility states.
82
+ *
83
+ * @param value - Raw value to validate.
84
+ * @returns Whether the value is a valid `AgentVisibility`.
85
+ *
86
+ * @public exported from `@promptbook/core`
87
+ */
88
+ function isAgentVisibility(value) {
89
+ return typeof value === 'string' && AGENT_VISIBILITY_VALUES.includes(value);
90
+ }
91
+ /**
92
+ * Normalizes raw visibility text into a supported value.
93
+ *
94
+ * @param value - Raw visibility value.
95
+ * @returns Normalized visibility, or `null` when invalid.
96
+ *
97
+ * @public exported from `@promptbook/core`
98
+ */
99
+ function normalizeAgentVisibility(value) {
100
+ if (typeof value !== 'string') {
101
+ return null;
102
+ }
103
+ const normalized = value.trim().toUpperCase();
104
+ return isAgentVisibility(normalized) ? normalized : null;
105
+ }
106
+ /**
107
+ * Parses visibility from an unknown value with a safe fallback.
108
+ *
109
+ * @param value - Raw visibility value.
110
+ * @param fallback - Fallback when the value is invalid.
111
+ * @returns Parsed visibility.
112
+ *
113
+ * @public exported from `@promptbook/core`
114
+ */
115
+ function parseAgentVisibility(value, fallback = DEFAULT_AGENT_VISIBILITY) {
116
+ var _a;
117
+ return (_a = normalizeAgentVisibility(value)) !== null && _a !== void 0 ? _a : fallback;
118
+ }
119
+ /**
120
+ * Parses visibility and throws when the value is not supported.
121
+ *
122
+ * @param value - Raw visibility value.
123
+ * @param sourceLabel - Human-readable source used in the error message.
124
+ * @returns Parsed visibility.
125
+ *
126
+ * @public exported from `@promptbook/core`
127
+ */
128
+ function parseAgentVisibilityStrict(value, sourceLabel = 'visibility') {
129
+ const visibility = normalizeAgentVisibility(value);
130
+ if (visibility) {
131
+ return visibility;
132
+ }
133
+ throw new ParseError(spaceTrim(`
134
+ Invalid ${sourceLabel}.
135
+
136
+ Value must be one of: ${AGENT_VISIBILITY_VALUES.map((allowedValue) => `\`${allowedValue}\``).join(', ')}.
137
+ `));
138
+ }
139
+ /**
140
+ * Extracts the last `META VISIBILITY` value from an agent source.
141
+ *
142
+ * @param agentSource - Raw book source.
143
+ * @param options - Strict parsing options.
144
+ * @returns Normalized visibility, or `null` when no commitment is present.
145
+ *
146
+ * @public exported from `@promptbook/core`
147
+ */
148
+ function parseAgentSourceVisibility(agentSource, options = {}) {
149
+ let visibilityContent = null;
150
+ for (const line of agentSource.split(/\r?\n/u)) {
151
+ const lineMatch = META_VISIBILITY_LINE_PATTERN.exec(line);
152
+ if (lineMatch) {
153
+ visibilityContent = lineMatch[2].trim();
154
+ }
155
+ }
156
+ if (visibilityContent === null) {
157
+ return null;
158
+ }
159
+ const visibility = normalizeAgentVisibility(visibilityContent);
160
+ if (visibility || !options.isStrict) {
161
+ return visibility;
162
+ }
163
+ return parseAgentVisibilityStrict(visibilityContent, '`META VISIBILITY` commitment');
164
+ }
165
+ /**
166
+ * Returns whether an agent should be listed publicly in anonymous views.
167
+ *
168
+ * @param visibility - Agent visibility to evaluate.
169
+ * @returns `true` for publicly listed agents.
170
+ *
171
+ * @public exported from `@promptbook/core`
172
+ */
173
+ function isPublicAgentVisibility(visibility) {
174
+ return visibility === 'PUBLIC';
175
+ }
176
+ /**
177
+ * Returns the next visibility in UI rotation order.
178
+ *
179
+ * @param visibility - Current visibility.
180
+ * @returns Next visibility value.
181
+ *
182
+ * @public exported from `@promptbook/core`
183
+ */
184
+ function getNextAgentVisibility(visibility) {
185
+ switch (visibility) {
186
+ case 'PRIVATE':
187
+ return 'UNLISTED';
188
+ case 'UNLISTED':
189
+ return 'PUBLIC';
190
+ case 'PUBLIC':
191
+ default:
192
+ return 'PRIVATE';
193
+ }
194
+ }
195
+ /**
196
+ * Inserts or replaces the `META VISIBILITY` commitment in a book source.
197
+ *
198
+ * @param agentSource - Raw book source.
199
+ * @param visibility - Visibility to persist.
200
+ * @returns Source with exactly one normalized `META VISIBILITY` line.
201
+ *
202
+ * @public exported from `@promptbook/core`
203
+ */
204
+ function setAgentSourceVisibility(agentSource, visibility) {
205
+ var _a;
206
+ const normalizedVisibility = parseAgentVisibilityStrict(visibility, '`META VISIBILITY` commitment');
207
+ const lines = agentSource.split(/\r?\n/u);
208
+ const nextLines = [];
209
+ let isVisibilityLineWritten = false;
210
+ for (const line of lines) {
211
+ const lineMatch = META_VISIBILITY_LINE_PATTERN.exec(line);
212
+ if (!lineMatch) {
213
+ nextLines.push(line);
214
+ continue;
215
+ }
216
+ if (isVisibilityLineWritten) {
217
+ continue;
218
+ }
219
+ nextLines.push(`${(_a = lineMatch[1]) !== null && _a !== void 0 ? _a : ''}META VISIBILITY ${normalizedVisibility}`);
220
+ isVisibilityLineWritten = true;
221
+ }
222
+ if (!isVisibilityLineWritten) {
223
+ nextLines.splice(findMetaVisibilityInsertIndex(nextLines), 0, `META VISIBILITY ${normalizedVisibility}`);
224
+ }
225
+ return nextLines.join('\n');
226
+ }
227
+ /**
228
+ * Finds the position after the first non-empty line, which is the agent title.
229
+ *
230
+ * @param lines - Source lines.
231
+ * @returns Insertion index for profile metadata.
232
+ */
233
+ function findMetaVisibilityInsertIndex(lines) {
234
+ const titleLineIndex = lines.findIndex((line) => line.trim().length > 0);
235
+ return titleLineIndex === -1 ? 0 : titleLineIndex + 1;
236
+ }
237
+ // Note: [💞] Ignore a discrepancy between file name and entity name
238
+
48
239
  /**
49
240
  * Class implementing take chain.
50
241
  *
@@ -1652,7 +1843,7 @@
1652
1843
  * @public exported from `@promptbook/utils`
1653
1844
  */
1654
1845
  function computeHash(value) {
1655
- return cryptoJs.SHA256(hexEncoder__default["default"].parse(spacetrim.spaceTrim(valueToString(value)))).toString( /* hex */);
1846
+ return CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(spacetrim.spaceTrim(valueToString(value)))).toString( /* hex */);
1656
1847
  }
1657
1848
  // TODO: [🥬][🥬] Use this ACRY
1658
1849
 
@@ -1768,20 +1959,6 @@
1768
1959
  }
1769
1960
  }
1770
1961
 
1771
- /**
1772
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
1773
- *
1774
- * @public exported from `@promptbook/core`
1775
- */
1776
- class ParseError extends Error {
1777
- constructor(message) {
1778
- super(message);
1779
- this.name = 'ParseError';
1780
- Object.setPrototypeOf(this, ParseError.prototype);
1781
- }
1782
- }
1783
- // TODO: Maybe split `ParseError` and `ApplyError`
1784
-
1785
1962
  /**
1786
1963
  * Function isValidJsonString will tell you if the string is valid JSON or not
1787
1964
  *
@@ -3772,7 +3949,7 @@
3772
3949
  * @private internal helper function
3773
3950
  */
3774
3951
  function $randomToken(randomness) {
3775
- return crypto.randomBytes(randomness).toString('hex');
3952
+ return CryptoJS__default["default"].lib.WordArray.random(randomness).toString(CryptoJS__default["default"].enc.Hex);
3776
3953
  }
3777
3954
  // TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
3778
3955
  // TODO: Maybe use nanoid instead https://github.com/ai/nanoid
@@ -5456,7 +5633,7 @@
5456
5633
  * @public exported from `@promptbook/editable`
5457
5634
  */
5458
5635
  function knowledgeSourceContentToName(knowledgeSourceContent) {
5459
- const hash = cryptoJs.SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
5636
+ const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
5460
5637
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5461
5638
  .toString( /* hex */)
5462
5639
  .substring(0, 20);
@@ -6369,7 +6546,7 @@
6369
6546
  console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
6370
6547
  value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
6371
6548
  }
6372
- const csv = papaparse.parse(value, settings);
6549
+ const csv = papaparse__default["default"].parse(value, settings);
6373
6550
  return csv;
6374
6551
  }
6375
6552
 
@@ -6454,10 +6631,10 @@
6454
6631
  i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
6455
6632
  );
6456
6633
  */
6457
- await onProgress(papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
6634
+ await onProgress(papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
6458
6635
  }
6459
6636
  }
6460
- return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6637
+ return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6461
6638
  },
6462
6639
  },
6463
6640
  {
@@ -6485,7 +6662,7 @@
6485
6662
  return /* not await */ mapCallback({ [key]: value }, index, array.length);
6486
6663
  }));
6487
6664
  }));
6488
- return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6665
+ return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6489
6666
  },
6490
6667
  },
6491
6668
  ],
@@ -9897,6 +10074,11 @@
9897
10074
  * - `isValidAgentUrl` *(this one)* which tests just agent URL
9898
10075
  * - `isValidPipelineUrl` which tests just pipeline URL
9899
10076
  *
10077
+ * Note: This is a pure structural validator and does not block private/internal network
10078
+ * addresses. Callers that fetch the URL server-side from an untrusted network context must
10079
+ * additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
10080
+ * outbound request.
10081
+ *
9900
10082
  * @public exported from `@promptbook/utils`
9901
10083
  */
9902
10084
  function isValidAgentUrl(url) {
@@ -9910,12 +10092,6 @@
9910
10092
  // TODO: [🐠]
9911
10093
  return false;
9912
10094
  }
9913
- /*
9914
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
9915
- if (isUrlOnPrivateNetwork(url)) {
9916
- return false;
9917
- }
9918
- */
9919
10095
  return true;
9920
10096
  }
9921
10097
  // TODO: [🐠] Maybe more info why the URL is invalid
@@ -10091,8 +10267,11 @@
10091
10267
  if (!trimmedContent) {
10092
10268
  return requirements;
10093
10269
  }
10094
- // Add goal as a proper h2 section to the system message
10095
- const goalSection = `## Goal\n\n${trimmedContent}`;
10270
+ const goalSection = spacetrim.spaceTrim((block) => `
10271
+ ## Goal
10272
+
10273
+ ${block(trimmedContent)}
10274
+ `);
10096
10275
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
10097
10276
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
10098
10277
  }
@@ -10614,8 +10793,11 @@
10614
10793
  if (!trimmedContent) {
10615
10794
  return requirements;
10616
10795
  }
10617
- // Add language as a bullet under a ## Language section
10618
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
10796
+ const languageSection = spacetrim.spaceTrim((block) => `
10797
+ ## Language
10798
+
10799
+ - Your language is ${block(trimmedContent)}
10800
+ `);
10619
10801
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
10620
10802
  }
10621
10803
  }
@@ -13062,21 +13244,22 @@
13062
13244
  * @private helper of `fractalAvatarVisual`
13063
13245
  */
13064
13246
  function drawDragonCurveLayer(context, points, options) {
13065
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
13247
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
13066
13248
  const firstPoint = points[0];
13067
13249
  const lastPoint = points[points.length - 1];
13068
13250
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
13069
13251
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
13070
13252
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
13071
13253
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
13254
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
13255
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
13072
13256
  context.save();
13073
13257
  context.beginPath();
13074
13258
  tracePolyline(context, points);
13075
- context.strokeStyle = `${shadowColor}82`;
13076
- context.lineWidth = strokeWidth * 1.8;
13259
+ context.strokeStyle = `${shadowColor}48`;
13260
+ context.lineWidth = strokeWidth * 4.5;
13077
13261
  context.lineJoin = 'round';
13078
13262
  context.lineCap = 'round';
13079
- context.filter = `blur(${size * 0.022}px)`;
13080
13263
  context.stroke();
13081
13264
  context.restore();
13082
13265
  context.beginPath();
@@ -13538,7 +13721,7 @@
13538
13721
  *
13539
13722
  * @private helper of `minecraft2AvatarVisual`
13540
13723
  */
13541
- const LIGHT_DIRECTION$2 = normalizeVector3({
13724
+ const LIGHT_DIRECTION$4 = normalizeVector3({
13542
13725
  x: 0.4,
13543
13726
  y: -0.65,
13544
13727
  z: 0.92,
@@ -13660,11 +13843,23 @@
13660
13843
  * @private helper of `minecraft2AvatarVisual`
13661
13844
  */
13662
13845
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
13846
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
13847
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
13848
+ const rx = size * (0.16 + interaction.intensity * 0.015);
13849
+ const ry = size * 0.055;
13850
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
13663
13851
  context.save();
13664
- context.fillStyle = `${palette.shadow}66`;
13665
- context.filter = `blur(${size * 0.02}px)`;
13852
+ context.translate(cx, cy);
13853
+ context.scale(1, ry / rx);
13854
+ const blurRadius = rx * 1.4;
13855
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
13856
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
13857
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
13858
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
13859
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
13860
+ context.fillStyle = shadowGradient;
13666
13861
  context.beginPath();
13667
- 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);
13862
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
13668
13863
  context.fill();
13669
13864
  context.restore();
13670
13865
  }
@@ -13750,7 +13945,7 @@
13750
13945
  corners: projectedCorners,
13751
13946
  texture: faceDefinition.texture,
13752
13947
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
13753
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
13948
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
13754
13949
  outlineColor: cuboid.outlineColor,
13755
13950
  };
13756
13951
  });
@@ -13888,13 +14083,27 @@
13888
14083
  spotlight.addColorStop(1, `${palette.highlight}00`);
13889
14084
  context.fillStyle = spotlight;
13890
14085
  context.fillRect(0, 0, size, size);
13891
- context.save();
13892
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
13893
- context.filter = `blur(${size * 0.018}px)`;
13894
- context.beginPath();
13895
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
13896
- context.fill();
13897
- context.restore();
14086
+ {
14087
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
14088
+ const cx = size * 0.5;
14089
+ const cy = size * 0.86;
14090
+ const rx = size * 0.2;
14091
+ const ry = size * 0.06;
14092
+ const blurRadius = rx * 1.4;
14093
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
14094
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
14095
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
14096
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
14097
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
14098
+ context.save();
14099
+ context.translate(cx, cy);
14100
+ context.scale(1, ry / rx);
14101
+ context.fillStyle = shadowGradient;
14102
+ context.beginPath();
14103
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
14104
+ context.fill();
14105
+ context.restore();
14106
+ }
13898
14107
  drawVoxelCuboid(context, {
13899
14108
  x: bodyX,
13900
14109
  y: bodyY,
@@ -14423,7 +14632,7 @@
14423
14632
  *
14424
14633
  * @private helper of `octopus3AvatarVisual`
14425
14634
  */
14426
- function formatAlphaHex$1(opacity) {
14635
+ function formatAlphaHex$2(opacity) {
14427
14636
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
14428
14637
  .toString(16)
14429
14638
  .padStart(2, '0');
@@ -14802,7 +15011,7 @@
14802
15011
  context.beginPath();
14803
15012
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
14804
15013
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
14805
- context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
15014
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
14806
15015
  context.lineWidth = radiusX * 0.08;
14807
15016
  context.lineCap = 'round';
14808
15017
  context.stroke();
@@ -14883,7 +15092,7 @@
14883
15092
  context.beginPath();
14884
15093
  context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
14885
15094
  context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
14886
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
15095
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
14887
15096
  context.lineWidth = projectedRadiusX * 0.08;
14888
15097
  context.lineCap = 'round';
14889
15098
  context.stroke();
@@ -14929,7 +15138,7 @@
14929
15138
  *
14930
15139
  * @private helper of the 3D octopus avatar visuals
14931
15140
  */
14932
- function formatAlphaHex(opacity) {
15141
+ function formatAlphaHex$1(opacity) {
14933
15142
  return Math.round(clampNumber$1(opacity, 0, 1) * 255)
14934
15143
  .toString(16)
14935
15144
  .padStart(2, '0');
@@ -14941,11 +15150,40 @@
14941
15150
  *
14942
15151
  * @private helper of `octopus3dAvatarVisual`
14943
15152
  */
14944
- const LIGHT_DIRECTION$1 = normalizeVector3({
15153
+ const LIGHT_DIRECTION$3 = normalizeVector3({
14945
15154
  x: 0.48,
14946
15155
  y: -0.62,
14947
15156
  z: 0.94,
14948
15157
  });
15158
+ /**
15159
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
15160
+ *
15161
+ * @private helper of `octopus3dAvatarVisual`
15162
+ */
15163
+ const octopus3dStableStateCache = new WeakMap();
15164
+ /**
15165
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
15166
+ *
15167
+ * @private helper of `octopus3dAvatarVisual`
15168
+ */
15169
+ function getOctopus3dStableState(createRandom) {
15170
+ const cached = octopus3dStableStateCache.get(createRandom);
15171
+ if (cached !== undefined) {
15172
+ return cached;
15173
+ }
15174
+ const animationRandom = createRandom('octopus3d-animation-profile');
15175
+ const eyeRandom = createRandom('octopus3d-eye-profile');
15176
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
15177
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
15178
+ const state = {
15179
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
15180
+ animationPhase: animationRandom() * Math.PI * 2,
15181
+ leftEyePhaseOffset,
15182
+ rightEyePhaseOffset,
15183
+ };
15184
+ octopus3dStableStateCache.set(createRandom, state);
15185
+ return state;
15186
+ }
14949
15187
  /**
14950
15188
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
14951
15189
  *
@@ -14958,10 +15196,7 @@
14958
15196
  isAnimated: true,
14959
15197
  supportsPointerTracking: true,
14960
15198
  render({ context, size, palette, createRandom, timeMs, interaction }) {
14961
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
14962
- const animationRandom = createRandom('octopus3d-animation-profile');
14963
- const eyeRandom = createRandom('octopus3d-eye-profile');
14964
- const animationPhase = animationRandom() * Math.PI * 2;
15199
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
14965
15200
  const sceneCenterX = size * 0.5;
14966
15201
  const sceneCenterY = size * 0.56;
14967
15202
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -15058,12 +15293,12 @@
15058
15293
  x: -faceEyeSpacing,
15059
15294
  y: faceEyeYOffset,
15060
15295
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
15061
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
15296
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15062
15297
  drawProjectedOrganicEye(context, {
15063
15298
  x: faceEyeSpacing,
15064
15299
  y: faceEyeYOffset,
15065
15300
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
15066
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
15301
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15067
15302
  drawProjectedOrganicMouth(context, [
15068
15303
  {
15069
15304
  x: -mouthHalfWidth,
@@ -15107,14 +15342,28 @@
15107
15342
  /**
15108
15343
  * Draws the soft ground shadow below the octopus.
15109
15344
  *
15345
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
15346
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
15347
+ *
15110
15348
  * @private helper of `octopus3dAvatarVisual`
15111
15349
  */
15112
15350
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
15351
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
15352
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
15353
+ const rx = size * (0.18 + interaction.intensity * 0.02);
15354
+ const ry = size * 0.06;
15113
15355
  context.save();
15114
- context.fillStyle = `${palette.shadow}66`;
15115
- context.filter = `blur(${size * 0.022}px)`;
15356
+ context.translate(cx, cy);
15357
+ context.scale(1, ry / rx);
15358
+ const blurRadius = rx * 1.4;
15359
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15360
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
15361
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
15362
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
15363
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
15364
+ context.fillStyle = shadowGradient;
15116
15365
  context.beginPath();
15117
- 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);
15366
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15118
15367
  context.fill();
15119
15368
  context.restore();
15120
15369
  }
@@ -15151,7 +15400,7 @@
15151
15400
  corners: projectedCorners,
15152
15401
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
15153
15402
  transformedCorners.length,
15154
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
15403
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
15155
15404
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
15156
15405
  outlineColor,
15157
15406
  });
@@ -15340,11 +15589,40 @@
15340
15589
  *
15341
15590
  * @private helper of `octopus3d2AvatarVisual`
15342
15591
  */
15343
- const LIGHT_DIRECTION = normalizeVector3({
15592
+ const LIGHT_DIRECTION$2 = normalizeVector3({
15344
15593
  x: 0.38,
15345
15594
  y: -0.6,
15346
15595
  z: 0.98,
15347
15596
  });
15597
+ /**
15598
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
15599
+ *
15600
+ * @private helper of `octopus3d2AvatarVisual`
15601
+ */
15602
+ const octopus3d2StableStateCache = new WeakMap();
15603
+ /**
15604
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
15605
+ *
15606
+ * @private helper of `octopus3d2AvatarVisual`
15607
+ */
15608
+ function getOctopus3d2StableState(createRandom) {
15609
+ const cached = octopus3d2StableStateCache.get(createRandom);
15610
+ if (cached !== undefined) {
15611
+ return cached;
15612
+ }
15613
+ const animationRandom = createRandom('octopus3d2-animation-profile');
15614
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
15615
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
15616
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
15617
+ const state = {
15618
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
15619
+ animationPhase: animationRandom() * Math.PI * 2,
15620
+ leftEyePhaseOffset,
15621
+ rightEyePhaseOffset,
15622
+ };
15623
+ octopus3d2StableStateCache.set(createRandom, state);
15624
+ return state;
15625
+ }
15348
15626
  /**
15349
15627
  * Octopus 3D 2 avatar visual.
15350
15628
  *
@@ -15357,10 +15635,7 @@
15357
15635
  isAnimated: true,
15358
15636
  supportsPointerTracking: true,
15359
15637
  render({ context, size, palette, createRandom, timeMs, interaction }) {
15360
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
15361
- const animationRandom = createRandom('octopus3d2-animation-profile');
15362
- const eyeRandom = createRandom('octopus3d2-eye-profile');
15363
- const animationPhase = animationRandom() * Math.PI * 2;
15638
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
15364
15639
  const sceneCenterX = size * 0.5;
15365
15640
  const sceneCenterY = size * 0.575;
15366
15641
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -15413,8 +15688,8 @@
15413
15688
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
15414
15689
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
15415
15690
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
15416
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15417
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15691
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15692
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15418
15693
  drawProjectedOrganicMouth(context, [
15419
15694
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
15420
15695
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -15443,54 +15718,118 @@
15443
15718
  /**
15444
15719
  * Draws the soft floor shadow that anchors the single mesh in the frame.
15445
15720
  *
15721
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
15722
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
15723
+ *
15446
15724
  * @private helper of `octopus3d2AvatarVisual`
15447
15725
  */
15448
15726
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
15727
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
15728
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
15729
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
15730
+ const ry = size * 0.062;
15449
15731
  context.save();
15450
- context.fillStyle = `${palette.shadow}66`;
15451
- context.filter = `blur(${size * 0.024}px)`;
15732
+ context.translate(cx, cy);
15733
+ context.scale(1, ry / rx);
15734
+ const blurRadius = rx * 1.4;
15735
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15736
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
15737
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
15738
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
15739
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
15740
+ context.fillStyle = shadowGradient;
15452
15741
  context.beginPath();
15453
- 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);
15742
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15454
15743
  context.fill();
15455
15744
  context.restore();
15456
15745
  }
15746
+ /**
15747
+ * Number of latitude segments used by the single blobby octopus mesh.
15748
+ *
15749
+ * @private helper of `octopus3d2AvatarVisual`
15750
+ */
15751
+ const LATITUDE_PATCH_COUNT$2 = 12;
15752
+ /**
15753
+ * Number of longitude segments used by the single blobby octopus mesh.
15754
+ *
15755
+ * @private helper of `octopus3d2AvatarVisual`
15756
+ */
15757
+ const LONGITUDE_PATCH_COUNT$2 = 24;
15457
15758
  /**
15458
15759
  * Resolves all visible projected patches for the single blobby octopus mesh.
15459
15760
  *
15761
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
15762
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
15763
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
15764
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
15765
+ * corners are shared between neighboring patches.
15766
+ *
15460
15767
  * @private helper of `octopus3d2AvatarVisual`
15461
15768
  */
15462
15769
  function resolveVisibleBlobbyOctopusPatches(options) {
15463
15770
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
15464
- const latitudePatchCount = 12;
15465
- const longitudePatchCount = 24;
15771
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
15772
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
15466
15773
  const surfacePatches = [];
15774
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
15775
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
15776
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
15777
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
15778
+ }
15779
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
15780
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
15781
+ }
15782
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
15783
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
15784
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
15785
+ }
15786
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
15787
+ const transformedCornerSamples = new Array(cornerCount);
15788
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
15789
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
15790
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
15791
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
15792
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
15793
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
15794
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
15795
+ }
15796
+ }
15467
15797
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
15468
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
15469
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
15798
+ const startLatitude = latitudeBoundaries[latitudeIndex];
15799
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
15470
15800
  const centerLatitude = (startLatitude + endLatitude) / 2;
15471
15801
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
15802
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
15803
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
15472
15804
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
15473
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
15474
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
15805
+ const startLongitude = longitudeBoundaries[longitudeIndex];
15806
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
15475
15807
  const centerLongitude = (startLongitude + endLongitude) / 2;
15476
- const localCorners = [
15477
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
15478
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
15479
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
15480
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
15808
+ const transformedCorners = [
15809
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
15810
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
15811
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
15812
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
15481
15813
  ];
15482
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
15483
15814
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
15484
15815
  if (surfaceNormal.z <= 0.01) {
15485
15816
  continue;
15486
15817
  }
15487
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
15818
+ const projectedCorners = [
15819
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
15820
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
15821
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
15822
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
15823
+ ];
15488
15824
  surfacePatches.push({
15489
15825
  corners: projectedCorners,
15490
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
15491
- transformedCorners.length,
15492
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
15493
- fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
15826
+ averageDepth: (transformedCorners[0].z +
15827
+ transformedCorners[1].z +
15828
+ transformedCorners[2].z +
15829
+ transformedCorners[3].z) /
15830
+ 4,
15831
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
15832
+ fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
15494
15833
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
15495
15834
  });
15496
15835
  }
@@ -15506,12 +15845,21 @@
15506
15845
  * @private helper of `octopus3d2AvatarVisual`
15507
15846
  */
15508
15847
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
15848
+ const { morphologyProfile, animationPhase, timeMs } = options;
15849
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
15850
+ }
15851
+ /**
15852
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
15853
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
15854
+ *
15855
+ * @private helper of `octopus3d2AvatarVisual`
15856
+ */
15857
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
15509
15858
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
15510
15859
  const cosineLatitude = Math.max(0, Math.cos(latitude));
15511
15860
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
15512
15861
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
15513
15862
  const lowerBlend = Math.pow(verticalProgress, 1.42);
15514
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
15515
15863
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
15516
15864
  const horizontalScale = 1.02 +
15517
15865
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -15549,7 +15897,7 @@
15549
15897
  *
15550
15898
  * @private helper of `octopus3d2AvatarVisual`
15551
15899
  */
15552
- function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
15900
+ function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
15553
15901
  const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
15554
15902
  if (tonalProgress < 0.16) {
15555
15903
  return palette.highlight;
@@ -15589,6 +15937,1384 @@
15589
15937
  context.restore();
15590
15938
  }
15591
15939
 
15940
+ /* eslint-disable no-magic-numbers */
15941
+ /**
15942
+ * Light direction used by the continuous octopus mesh shading.
15943
+ *
15944
+ * @private helper of `octopus3d3AvatarVisual`
15945
+ */
15946
+ const LIGHT_DIRECTION$1 = normalizeVector3({
15947
+ x: 0.34,
15948
+ y: -0.62,
15949
+ z: 1,
15950
+ });
15951
+ /**
15952
+ * Real-octopus tentacle count used by the continuous lower mesh.
15953
+ *
15954
+ * @private helper of `octopus3d3AvatarVisual`
15955
+ */
15956
+ const OCTOPUS_TENTACLE_COUNT$1 = 8;
15957
+ /**
15958
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
15959
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
15960
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
15961
+ *
15962
+ * @private helper of `octopus3d3AvatarVisual`
15963
+ */
15964
+ const stableStateCache = new WeakMap();
15965
+ /**
15966
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
15967
+ * result on every subsequent call within the same `<Avatar/>` mount.
15968
+ *
15969
+ * @private helper of `octopus3d3AvatarVisual`
15970
+ */
15971
+ function getOctopus3d3StableState(createRandom) {
15972
+ const cached = stableStateCache.get(createRandom);
15973
+ if (cached !== undefined) {
15974
+ return cached;
15975
+ }
15976
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
15977
+ const animationRandom = createRandom('octopus3d3-animation-profile');
15978
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
15979
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
15980
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
15981
+ const state = {
15982
+ morphologyProfile,
15983
+ animationPhase: animationRandom() * Math.PI * 2,
15984
+ leftEyePhaseOffset,
15985
+ rightEyePhaseOffset,
15986
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
15987
+ };
15988
+ stableStateCache.set(createRandom, state);
15989
+ return state;
15990
+ }
15991
+ /**
15992
+ * Octopus 3D 3 avatar visual.
15993
+ *
15994
+ * @private built-in avatar visual
15995
+ */
15996
+ const octopus3d3AvatarVisual = {
15997
+ id: 'octopus3d3',
15998
+ title: 'Octopus 3D 3',
15999
+ description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
16000
+ isAnimated: true,
16001
+ supportsPointerTracking: true,
16002
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
16003
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
16004
+ const sceneCenterX = size * 0.5;
16005
+ const sceneCenterY = size * 0.535;
16006
+ const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
16007
+ const meshCenter = {
16008
+ x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
16009
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
16010
+ z: interaction.intensity * size * 0.018,
16011
+ };
16012
+ const rotationY = -0.1 +
16013
+ Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
16014
+ interaction.bodyOffsetX * 0.22 +
16015
+ interaction.gazeX * 0.88;
16016
+ const rotationX = -0.07 +
16017
+ Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
16018
+ interaction.bodyOffsetY * 0.08 -
16019
+ interaction.gazeY * 0.38;
16020
+ const surfaceOptions = {
16021
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
16022
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
16023
+ radiusZ: size *
16024
+ morphologyProfile.body.bodyRadiusRatio *
16025
+ (1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
16026
+ morphologyProfile,
16027
+ timeMs,
16028
+ animationPhase,
16029
+ tentacleProfiles,
16030
+ };
16031
+ const surfacePatches = resolveVisibleContinuousOctopusPatches({
16032
+ ...surfaceOptions,
16033
+ center: meshCenter,
16034
+ rotationX,
16035
+ rotationY,
16036
+ sceneCenterX,
16037
+ sceneCenterY,
16038
+ size,
16039
+ palette,
16040
+ });
16041
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
16042
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
16043
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
16044
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
16045
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
16046
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
16047
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
16048
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
16049
+ drawAvatarFrame(context, size, palette);
16050
+ drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
16051
+ drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
16052
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
16053
+ drawContinuousSurfacePatch(context, surfacePatch);
16054
+ }
16055
+ drawProjectedSurfaceCurrents({
16056
+ context,
16057
+ surfaceOptions,
16058
+ center: meshCenter,
16059
+ rotationX,
16060
+ rotationY,
16061
+ sceneCenterX,
16062
+ sceneCenterY,
16063
+ size,
16064
+ palette,
16065
+ morphologyProfile,
16066
+ timeMs,
16067
+ animationPhase,
16068
+ });
16069
+ drawProjectedTentacleSuckers({
16070
+ context,
16071
+ surfaceOptions,
16072
+ center: meshCenter,
16073
+ rotationX,
16074
+ rotationY,
16075
+ sceneCenterX,
16076
+ sceneCenterY,
16077
+ size,
16078
+ palette,
16079
+ });
16080
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16081
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16082
+ drawProjectedOrganicMouth(context, [
16083
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
16084
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
16085
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
16086
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
16087
+ },
16088
+ };
16089
+ /**
16090
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
16091
+ *
16092
+ * @private helper of `octopus3d3AvatarVisual`
16093
+ */
16094
+ function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
16095
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
16096
+ const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
16097
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
16098
+ return {
16099
+ centerLongitude: -Math.PI * 0.86 +
16100
+ progress * Math.PI * 1.72 +
16101
+ (tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
16102
+ widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
16103
+ lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
16104
+ swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
16105
+ depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
16106
+ phase: tentacleRandom() * Math.PI * 2,
16107
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
16108
+ };
16109
+ });
16110
+ }
16111
+ /**
16112
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
16113
+ *
16114
+ * @private helper of `octopus3d3AvatarVisual`
16115
+ */
16116
+ function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
16117
+ 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));
16118
+ glowGradient.addColorStop(0, `${palette.highlight}66`);
16119
+ glowGradient.addColorStop(0.34, `${palette.accent}2e`);
16120
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
16121
+ context.fillStyle = glowGradient;
16122
+ context.fillRect(0, 0, size, size);
16123
+ 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);
16124
+ lowerGradient.addColorStop(0, `${palette.secondary}25`);
16125
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
16126
+ context.fillStyle = lowerGradient;
16127
+ context.fillRect(0, 0, size, size);
16128
+ }
16129
+ /**
16130
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
16131
+ *
16132
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
16133
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
16134
+ *
16135
+ * @private helper of `octopus3d3AvatarVisual`
16136
+ */
16137
+ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
16138
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
16139
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
16140
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
16141
+ const ry = size * 0.06;
16142
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
16143
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
16144
+ context.save();
16145
+ context.translate(cx, cy);
16146
+ context.scale(1, ry / rx);
16147
+ const blurRadius = rx * 1.4;
16148
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16149
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16150
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16151
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16152
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16153
+ context.fillStyle = shadowGradient;
16154
+ context.beginPath();
16155
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16156
+ context.fill();
16157
+ context.restore();
16158
+ }
16159
+ /**
16160
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
16161
+ *
16162
+ * @private helper of `octopus3d3AvatarVisual`
16163
+ */
16164
+ const LATITUDE_PATCH_COUNT$1 = 16;
16165
+ /**
16166
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
16167
+ *
16168
+ * @private helper of `octopus3d3AvatarVisual`
16169
+ */
16170
+ const LONGITUDE_PATCH_COUNT$1 = 40;
16171
+ /**
16172
+ * Resolves visible projected patches for the continuous octopus mesh.
16173
+ *
16174
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
16175
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
16176
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
16177
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
16178
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
16179
+ *
16180
+ * @private helper of `octopus3d3AvatarVisual`
16181
+ */
16182
+ function resolveVisibleContinuousOctopusPatches(options) {
16183
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
16184
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
16185
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
16186
+ const surfacePatches = [];
16187
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
16188
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
16189
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
16190
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
16191
+ }
16192
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16193
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
16194
+ }
16195
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
16196
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
16197
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
16198
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
16199
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
16200
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16201
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
16202
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
16203
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
16204
+ }
16205
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
16206
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
16207
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
16208
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
16209
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
16210
+ }
16211
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
16212
+ const transformedCornerSamples = new Array(cornerCount);
16213
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
16214
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
16215
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
16216
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
16217
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
16218
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
16219
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
16220
+ }
16221
+ }
16222
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
16223
+ const startLatitude = latitudeBoundaries[latitudeIndex];
16224
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
16225
+ const centerLatitude = (startLatitude + endLatitude) / 2;
16226
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
16227
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
16228
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
16229
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
16230
+ const transformedCorners = [
16231
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
16232
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
16233
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
16234
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
16235
+ ];
16236
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
16237
+ if (surfaceNormal.z <= 0.008) {
16238
+ continue;
16239
+ }
16240
+ const projectedCorners = [
16241
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
16242
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
16243
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
16244
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
16245
+ ];
16246
+ surfacePatches.push({
16247
+ corners: projectedCorners,
16248
+ averageDepth: (transformedCorners[0].z +
16249
+ transformedCorners[1].z +
16250
+ transformedCorners[2].z +
16251
+ transformedCorners[3].z) /
16252
+ 4,
16253
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
16254
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
16255
+ outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
16256
+ });
16257
+ }
16258
+ }
16259
+ return surfacePatches;
16260
+ }
16261
+ /**
16262
+ * Samples one point on the continuous Octopus 3D 3 surface.
16263
+ *
16264
+ * The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
16265
+ * tentacled while still being rendered as one connected blobby mesh.
16266
+ *
16267
+ * @private helper of `octopus3d3AvatarVisual`
16268
+ */
16269
+ function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
16270
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
16271
+ }
16272
+ /**
16273
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
16274
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
16275
+ *
16276
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
16277
+ * corner grid, so the same longitude is reused across every latitude row and each
16278
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
16279
+ *
16280
+ * @private helper of `octopus3d3AvatarVisual`
16281
+ */
16282
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
16283
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
16284
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
16285
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
16286
+ const upperBlend = Math.pow(1 - verticalProgress, 1.28);
16287
+ const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
16288
+ const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
16289
+ const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
16290
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
16291
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
16292
+ animationPhase * 0.6 +
16293
+ timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
16294
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
16295
+ (0.3 + lowerBlend * 0.7);
16296
+ const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
16297
+ lowerBlend *
16298
+ tentacleInfluence.core *
16299
+ tentacleInfluence.swayScale;
16300
+ const horizontalScale = 1.04 +
16301
+ mantleRipple +
16302
+ lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
16303
+ lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
16304
+ upperBlend * 0.08;
16305
+ const depthScale = 1.06 +
16306
+ upperBlend * 0.16 +
16307
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
16308
+ lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
16309
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
16310
+ const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
16311
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
16312
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
16313
+ const lowerDrop = lowerBlend *
16314
+ radiusY *
16315
+ (0.18 +
16316
+ tentacleInfluence.core *
16317
+ (0.38 +
16318
+ tentacleInfluence.lengthScale * 0.22 +
16319
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
16320
+ return {
16321
+ x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
16322
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
16323
+ upperBlend * radiusY * 0.1 +
16324
+ lowerDrop +
16325
+ Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
16326
+ Math.cos(timeMs / 880 + tentacleInfluence.phase) *
16327
+ lowerBlend *
16328
+ tipBlend *
16329
+ tentacleInfluence.core *
16330
+ radiusY *
16331
+ 0.034,
16332
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
16333
+ Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
16334
+ lowerBlend *
16335
+ tentacleInfluence.core *
16336
+ radiusZ *
16337
+ 0.04,
16338
+ };
16339
+ }
16340
+ /**
16341
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
16342
+ *
16343
+ * @private helper of `octopus3d3AvatarVisual`
16344
+ */
16345
+ function resolveContinuousTentacleInfluence(options, longitude) {
16346
+ let totalWeight = 0;
16347
+ let weightedSin = 0;
16348
+ let weightedCos = 0;
16349
+ let weightedWidthScale = 0;
16350
+ let weightedLengthScale = 0;
16351
+ let weightedSwayScale = 0;
16352
+ let weightedDepthScale = 0;
16353
+ let weightedPhase = 0;
16354
+ for (const tentacleProfile of options.tentacleProfiles) {
16355
+ const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
16356
+ const width = 0.2 * tentacleProfile.widthScale;
16357
+ const weight = Math.exp(-(distance * distance) / (width * width));
16358
+ totalWeight += weight;
16359
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
16360
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
16361
+ weightedWidthScale += tentacleProfile.widthScale * weight;
16362
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
16363
+ weightedSwayScale += tentacleProfile.swayScale * weight;
16364
+ weightedDepthScale += tentacleProfile.depthScale * weight;
16365
+ weightedPhase += tentacleProfile.phase * weight;
16366
+ }
16367
+ if (totalWeight < 0.0001) {
16368
+ return {
16369
+ core: 0,
16370
+ centerLongitude: longitude,
16371
+ widthScale: 1,
16372
+ lengthScale: 1,
16373
+ swayScale: 1,
16374
+ depthScale: 1,
16375
+ phase: 0,
16376
+ };
16377
+ }
16378
+ return {
16379
+ core: clampNumber$1(totalWeight, 0, 1),
16380
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
16381
+ widthScale: weightedWidthScale / totalWeight,
16382
+ lengthScale: weightedLengthScale / totalWeight,
16383
+ swayScale: weightedSwayScale / totalWeight,
16384
+ depthScale: weightedDepthScale / totalWeight,
16385
+ phase: weightedPhase / totalWeight,
16386
+ };
16387
+ }
16388
+ /**
16389
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
16390
+ *
16391
+ * @private helper of `octopus3d3AvatarVisual`
16392
+ */
16393
+ function resolveContinuousLobeWave(options, longitude) {
16394
+ const { morphologyProfile, animationPhase, timeMs } = options;
16395
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
16396
+ animationPhase +
16397
+ timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
16398
+ 1) /
16399
+ 2);
16400
+ }
16401
+ /**
16402
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
16403
+ *
16404
+ * @private helper of `octopus3d3AvatarVisual`
16405
+ */
16406
+ function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
16407
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
16408
+ if (tonalProgress < 0.14) {
16409
+ return palette.highlight;
16410
+ }
16411
+ if (tonalProgress < 0.32) {
16412
+ return palette.secondary;
16413
+ }
16414
+ if (tonalProgress < 0.72) {
16415
+ return forwardness > 0.55 ? palette.secondary : palette.primary;
16416
+ }
16417
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
16418
+ }
16419
+ /**
16420
+ * Draws one projected mesh patch with soft shading and a subtle edge.
16421
+ *
16422
+ * @private helper of `octopus3d3AvatarVisual`
16423
+ */
16424
+ function drawContinuousSurfacePatch(context, surfacePatch) {
16425
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
16426
+ if (surfacePatch.lightIntensity > 0) {
16427
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
16428
+ }
16429
+ else if (surfacePatch.lightIntensity < 0) {
16430
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
16431
+ }
16432
+ context.save();
16433
+ context.beginPath();
16434
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
16435
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
16436
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
16437
+ }
16438
+ context.closePath();
16439
+ context.strokeStyle = surfacePatch.outlineColor;
16440
+ context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
16441
+ context.lineJoin = 'round';
16442
+ context.stroke();
16443
+ context.restore();
16444
+ }
16445
+ /**
16446
+ * Draws projected mantle-current lines on the front of the mesh.
16447
+ *
16448
+ * @private helper of `octopus3d3AvatarVisual`
16449
+ */
16450
+ function drawProjectedSurfaceCurrents(options) {
16451
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
16452
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
16453
+ const centerIndex = (currentCount - 1) / 2;
16454
+ context.save();
16455
+ context.lineCap = 'round';
16456
+ context.lineJoin = 'round';
16457
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
16458
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
16459
+ const projectedPoints = [];
16460
+ for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
16461
+ const progress = sampleIndex / 7;
16462
+ const latitude = -0.46 + progress * 0.74;
16463
+ const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
16464
+ const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
16465
+ if (scenePoint.z > center.z - size * 0.016) {
16466
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
16467
+ }
16468
+ }
16469
+ if (projectedPoints.length < 3) {
16470
+ continue;
16471
+ }
16472
+ context.beginPath();
16473
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
16474
+ for (const projectedPoint of projectedPoints.slice(1)) {
16475
+ context.lineTo(projectedPoint.x, projectedPoint.y);
16476
+ }
16477
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
16478
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
16479
+ context.stroke();
16480
+ }
16481
+ context.restore();
16482
+ }
16483
+ /**
16484
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
16485
+ *
16486
+ * @private helper of `octopus3d3AvatarVisual`
16487
+ */
16488
+ function drawProjectedTentacleSuckers(options) {
16489
+ const { surfaceOptions, size } = options;
16490
+ const { timeMs } = surfaceOptions;
16491
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
16492
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
16493
+ continue;
16494
+ }
16495
+ for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
16496
+ const latitude = 0.52 + suckerIndex * 0.14;
16497
+ const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
16498
+ const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
16499
+ drawProjectedSurfaceSpot({
16500
+ ...options,
16501
+ latitude,
16502
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
16503
+ radiusScale: size * (0.0065 - suckerIndex * 0.0007),
16504
+ });
16505
+ }
16506
+ }
16507
+ }
16508
+ /**
16509
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
16510
+ *
16511
+ * @private helper of `octopus3d3AvatarVisual`
16512
+ */
16513
+ function drawProjectedSurfaceSpot(options) {
16514
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
16515
+ const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
16516
+ const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
16517
+ const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
16518
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
16519
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
16520
+ return;
16521
+ }
16522
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
16523
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
16524
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
16525
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
16526
+ radiusScale *
16527
+ 0.74, size * 0.003, size * 0.018);
16528
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
16529
+ radiusScale *
16530
+ 0.52, size * 0.0024, size * 0.014);
16531
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
16532
+ context.save();
16533
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
16534
+ context.rotate(rotation);
16535
+ context.beginPath();
16536
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
16537
+ context.fillStyle = `${palette.highlight}73`;
16538
+ context.fill();
16539
+ context.strokeStyle = `${palette.highlight}99`;
16540
+ context.lineWidth = Math.max(0.7, size * 0.0028);
16541
+ context.stroke();
16542
+ context.restore();
16543
+ }
16544
+ /**
16545
+ * Resolves a signed angular distance from the source longitude to the target longitude.
16546
+ *
16547
+ * @private helper of `octopus3d3AvatarVisual`
16548
+ */
16549
+ function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
16550
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
16551
+ }
16552
+ /**
16553
+ * Smoothly maps a value between two bounds into `[0, 1]`.
16554
+ *
16555
+ * @private helper of `octopus3d3AvatarVisual`
16556
+ */
16557
+ function smoothStep$1(edgeStart, edgeEnd, value) {
16558
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
16559
+ return progress * progress * (3 - 2 * progress);
16560
+ }
16561
+
16562
+ /* eslint-disable no-magic-numbers */
16563
+ /**
16564
+ * Light direction used by the continuous Octopus 3D 4 mesh shading.
16565
+ *
16566
+ * @private helper of `octopus3d4AvatarVisual`
16567
+ */
16568
+ const LIGHT_DIRECTION = normalizeVector3({
16569
+ x: 0.32,
16570
+ y: -0.66,
16571
+ z: 1,
16572
+ });
16573
+ /**
16574
+ * Rim-light direction used to brighten the side of the silhouette.
16575
+ *
16576
+ * @private helper of `octopus3d4AvatarVisual`
16577
+ */
16578
+ const RIM_LIGHT_DIRECTION = normalizeVector3({
16579
+ x: -0.7,
16580
+ y: -0.18,
16581
+ z: 0.55,
16582
+ });
16583
+ /**
16584
+ * Real-octopus tentacle count used by the continuous lower mesh.
16585
+ *
16586
+ * @private helper of `octopus3d4AvatarVisual`
16587
+ */
16588
+ const OCTOPUS_TENTACLE_COUNT = 8;
16589
+ /**
16590
+ * Number of seeded skin spots painted across the octopus body.
16591
+ *
16592
+ * @private helper of `octopus3d4AvatarVisual`
16593
+ */
16594
+ const SKIN_SPOT_COUNT = 14;
16595
+ /**
16596
+ * Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
16597
+ *
16598
+ * @private helper of `octopus3d4AvatarVisual`
16599
+ */
16600
+ const octopus3d4StableStateCache = new WeakMap();
16601
+ /**
16602
+ * Returns the stable per-avatar state, computing it on first access and caching afterwards.
16603
+ *
16604
+ * @private helper of `octopus3d4AvatarVisual`
16605
+ */
16606
+ function getOctopus3d4StableState(createRandom) {
16607
+ const cached = octopus3d4StableStateCache.get(createRandom);
16608
+ if (cached !== undefined) {
16609
+ return cached;
16610
+ }
16611
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16612
+ const animationRandom = createRandom('octopus3d4-animation-profile');
16613
+ const eyeRandom = createRandom('octopus3d4-eye-profile');
16614
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
16615
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
16616
+ const state = {
16617
+ morphologyProfile,
16618
+ animationPhase: animationRandom() * Math.PI * 2,
16619
+ leftEyePhaseOffset,
16620
+ rightEyePhaseOffset,
16621
+ tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
16622
+ skinSpots: createBlobbySkinSpots(createRandom),
16623
+ };
16624
+ octopus3d4StableStateCache.set(createRandom, state);
16625
+ return state;
16626
+ }
16627
+ /**
16628
+ * Octopus 3D 4 avatar visual.
16629
+ *
16630
+ * @private built-in avatar visual
16631
+ */
16632
+ const octopus3d4AvatarVisual = {
16633
+ id: 'octopus3d4',
16634
+ title: 'Octopus 3D 4',
16635
+ 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.',
16636
+ isAnimated: true,
16637
+ supportsPointerTracking: true,
16638
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
16639
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
16640
+ const sceneCenterX = size * 0.5;
16641
+ const sceneCenterY = size * 0.535;
16642
+ const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
16643
+ const meshCenter = {
16644
+ x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
16645
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
16646
+ z: interaction.intensity * size * 0.02,
16647
+ };
16648
+ const rotationY = -0.08 +
16649
+ Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
16650
+ interaction.bodyOffsetX * 0.24 +
16651
+ interaction.gazeX * 0.98;
16652
+ const rotationX = -0.07 +
16653
+ Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
16654
+ interaction.bodyOffsetY * 0.08 -
16655
+ interaction.gazeY * 0.42;
16656
+ const surfaceOptions = {
16657
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
16658
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
16659
+ radiusZ: size *
16660
+ morphologyProfile.body.bodyRadiusRatio *
16661
+ (1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
16662
+ morphologyProfile,
16663
+ timeMs,
16664
+ animationPhase,
16665
+ tentacleProfiles,
16666
+ };
16667
+ const surfacePatches = resolveVisibleBlobbyContinuousPatches({
16668
+ ...surfaceOptions,
16669
+ center: meshCenter,
16670
+ rotationX,
16671
+ rotationY,
16672
+ sceneCenterX,
16673
+ sceneCenterY,
16674
+ size,
16675
+ palette,
16676
+ });
16677
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
16678
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
16679
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
16680
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
16681
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
16682
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
16683
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
16684
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
16685
+ drawAvatarFrame(context, size, palette);
16686
+ drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
16687
+ drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
16688
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
16689
+ drawBlobbyContinuousSurfacePatch(context, surfacePatch);
16690
+ }
16691
+ drawBlobbySkinSpots({
16692
+ context,
16693
+ surfaceOptions,
16694
+ center: meshCenter,
16695
+ rotationX,
16696
+ rotationY,
16697
+ sceneCenterX,
16698
+ sceneCenterY,
16699
+ size,
16700
+ palette,
16701
+ skinSpots,
16702
+ });
16703
+ drawBlobbyContinuousCurrents({
16704
+ context,
16705
+ surfaceOptions,
16706
+ center: meshCenter,
16707
+ rotationX,
16708
+ rotationY,
16709
+ sceneCenterX,
16710
+ sceneCenterY,
16711
+ size,
16712
+ palette,
16713
+ morphologyProfile,
16714
+ timeMs,
16715
+ animationPhase,
16716
+ });
16717
+ drawBlobbyContinuousSuckers({
16718
+ context,
16719
+ surfaceOptions,
16720
+ center: meshCenter,
16721
+ rotationX,
16722
+ rotationY,
16723
+ sceneCenterX,
16724
+ sceneCenterY,
16725
+ size,
16726
+ palette,
16727
+ });
16728
+ drawBlobbyContinuousGloss({
16729
+ context,
16730
+ surfaceOptions,
16731
+ center: meshCenter,
16732
+ rotationX,
16733
+ rotationY,
16734
+ sceneCenterX,
16735
+ sceneCenterY,
16736
+ size,
16737
+ palette,
16738
+ });
16739
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16740
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16741
+ drawProjectedOrganicMouth(context, [
16742
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
16743
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
16744
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
16745
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
16746
+ },
16747
+ };
16748
+ /**
16749
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
16750
+ *
16751
+ * @private helper of `octopus3d4AvatarVisual`
16752
+ */
16753
+ function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
16754
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
16755
+ const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
16756
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
16757
+ return {
16758
+ centerLongitude: -Math.PI * 0.9 +
16759
+ progress * Math.PI * 1.8 +
16760
+ (tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
16761
+ widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
16762
+ lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
16763
+ swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
16764
+ depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
16765
+ curlScale: 0.62 + tentacleRandom() * 0.46,
16766
+ primaryPhase: tentacleRandom() * Math.PI * 2,
16767
+ secondaryPhase: tentacleRandom() * Math.PI * 2,
16768
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
16769
+ };
16770
+ });
16771
+ }
16772
+ /**
16773
+ * Creates seeded skin pigment spots distributed across the upper octopus mesh.
16774
+ *
16775
+ * @private helper of `octopus3d4AvatarVisual`
16776
+ */
16777
+ function createBlobbySkinSpots(createRandom) {
16778
+ const spotRandom = createRandom('octopus3d4-skin-spots');
16779
+ return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
16780
+ latitude: -0.45 + spotRandom() * 0.7,
16781
+ longitude: -0.5 + spotRandom() * 1.0,
16782
+ radiusScale: 0.0028 + spotRandom() * 0.0052,
16783
+ opacity: 0.16 + spotRandom() * 0.22,
16784
+ }));
16785
+ }
16786
+ /**
16787
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
16788
+ *
16789
+ * @private helper of `octopus3d4AvatarVisual`
16790
+ */
16791
+ function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
16792
+ 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));
16793
+ glowGradient.addColorStop(0, `${palette.highlight}74`);
16794
+ glowGradient.addColorStop(0.32, `${palette.accent}32`);
16795
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
16796
+ context.fillStyle = glowGradient;
16797
+ context.fillRect(0, 0, size, size);
16798
+ 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);
16799
+ lowerGradient.addColorStop(0, `${palette.secondary}28`);
16800
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
16801
+ context.fillStyle = lowerGradient;
16802
+ context.fillRect(0, 0, size, size);
16803
+ }
16804
+ /**
16805
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
16806
+ *
16807
+ * @private helper of `octopus3d4AvatarVisual`
16808
+ */
16809
+ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
16810
+ const cx = size * 0.5 + interaction.gazeX * size * 0.046;
16811
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
16812
+ const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
16813
+ const ry = size * 0.062;
16814
+ context.save();
16815
+ context.translate(cx, cy);
16816
+ context.scale(1, ry / rx);
16817
+ const blurRadius = rx * 1.42;
16818
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16819
+ shadowGradient.addColorStop(0, `${palette.shadow}82`);
16820
+ shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
16821
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
16822
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16823
+ context.fillStyle = shadowGradient;
16824
+ context.beginPath();
16825
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16826
+ context.fill();
16827
+ context.restore();
16828
+ }
16829
+ /**
16830
+ * Number of latitude segments used by the continuous Octopus 3D 4 mesh.
16831
+ *
16832
+ * @private helper of `octopus3d4AvatarVisual`
16833
+ */
16834
+ const LATITUDE_PATCH_COUNT = 20;
16835
+ /**
16836
+ * Number of longitude segments used by the continuous Octopus 3D 4 mesh.
16837
+ *
16838
+ * @private helper of `octopus3d4AvatarVisual`
16839
+ */
16840
+ const LONGITUDE_PATCH_COUNT = 48;
16841
+ /**
16842
+ * Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
16843
+ *
16844
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
16845
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
16846
+ * than re-evaluated for every patch corner.
16847
+ *
16848
+ * @private helper of `octopus3d4AvatarVisual`
16849
+ */
16850
+ function resolveVisibleBlobbyContinuousPatches(options) {
16851
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
16852
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
16853
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
16854
+ const surfacePatches = [];
16855
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
16856
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
16857
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
16858
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
16859
+ }
16860
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16861
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
16862
+ }
16863
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
16864
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
16865
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
16866
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
16867
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
16868
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16869
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
16870
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
16871
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
16872
+ }
16873
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
16874
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
16875
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
16876
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
16877
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
16878
+ }
16879
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
16880
+ const transformedCornerSamples = new Array(cornerCount);
16881
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
16882
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
16883
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
16884
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
16885
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
16886
+ const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
16887
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
16888
+ }
16889
+ }
16890
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
16891
+ const startLatitude = latitudeBoundaries[latitudeIndex];
16892
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
16893
+ const centerLatitude = (startLatitude + endLatitude) / 2;
16894
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
16895
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
16896
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
16897
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
16898
+ const transformedCorners = [
16899
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
16900
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
16901
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
16902
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
16903
+ ];
16904
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
16905
+ if (surfaceNormal.z <= 0.006) {
16906
+ continue;
16907
+ }
16908
+ const projectedCorners = [
16909
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
16910
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
16911
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
16912
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
16913
+ ];
16914
+ const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
16915
+ const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
16916
+ surfacePatches.push({
16917
+ corners: projectedCorners,
16918
+ averageDepth: (transformedCorners[0].z +
16919
+ transformedCorners[1].z +
16920
+ transformedCorners[2].z +
16921
+ transformedCorners[3].z) /
16922
+ 4,
16923
+ lightIntensity,
16924
+ rimLightIntensity,
16925
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
16926
+ outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
16927
+ });
16928
+ }
16929
+ }
16930
+ return surfacePatches;
16931
+ }
16932
+ /**
16933
+ * Samples one point on the continuous Octopus 3D 4 surface.
16934
+ *
16935
+ * @private helper of `octopus3d4AvatarVisual`
16936
+ */
16937
+ function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
16938
+ return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
16939
+ }
16940
+ /**
16941
+ * Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
16942
+ *
16943
+ * The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
16944
+ * across every latitude row and each tentacle/lobe value is computed once per frame instead
16945
+ * of `latitudePatchCount * 4` times.
16946
+ *
16947
+ * @private helper of `octopus3d4AvatarVisual`
16948
+ */
16949
+ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
16950
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
16951
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
16952
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
16953
+ const upperBlend = Math.pow(1 - verticalProgress, 1.32);
16954
+ const lowerBlend = smoothStep(0.34, 1, verticalProgress);
16955
+ const tipBlend = smoothStep(0.66, 1, verticalProgress);
16956
+ const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
16957
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
16958
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
16959
+ animationPhase * 0.6 +
16960
+ timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
16961
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
16962
+ (0.32 + lowerBlend * 0.7);
16963
+ const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
16964
+ lowerBlend *
16965
+ tentacleInfluence.core *
16966
+ tentacleInfluence.swayScale;
16967
+ const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
16968
+ lowerBlend *
16969
+ tentacleInfluence.core *
16970
+ tentacleInfluence.swayScale *
16971
+ 0.42;
16972
+ const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
16973
+ tipBlend *
16974
+ tentacleInfluence.core *
16975
+ tentacleInfluence.curlScale *
16976
+ 0.36;
16977
+ const horizontalScale = 1.06 +
16978
+ mantleRipple +
16979
+ lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
16980
+ lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
16981
+ upperBlend * 0.08;
16982
+ const depthScale = 1.08 +
16983
+ upperBlend * 0.16 +
16984
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
16985
+ lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
16986
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
16987
+ const tentacleTubeRadius = lowerBlend *
16988
+ tentacleInfluence.core *
16989
+ (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
16990
+ radiusX;
16991
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
16992
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
16993
+ const lowerDrop = lowerBlend *
16994
+ radiusY *
16995
+ (0.2 +
16996
+ tentacleInfluence.core *
16997
+ (0.42 +
16998
+ tentacleInfluence.lengthScale * 0.24 +
16999
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
17000
+ const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
17001
+ return {
17002
+ x: Math.sin(effectiveLongitude) * planarRadiusX +
17003
+ combinedTentacleSway +
17004
+ tentacleCurl * radiusX * 0.18,
17005
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
17006
+ upperBlend * radiusY * 0.12 +
17007
+ lowerDrop +
17008
+ Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
17009
+ Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
17010
+ lowerBlend *
17011
+ tipBlend *
17012
+ tentacleInfluence.core *
17013
+ radiusY *
17014
+ 0.04,
17015
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
17016
+ Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
17017
+ lowerBlend *
17018
+ tentacleInfluence.core *
17019
+ radiusZ *
17020
+ 0.044 +
17021
+ tentacleCurl * radiusZ * 0.14,
17022
+ };
17023
+ }
17024
+ /**
17025
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
17026
+ *
17027
+ * @private helper of `octopus3d4AvatarVisual`
17028
+ */
17029
+ function resolveBlobbyTentacleInfluence(options, longitude) {
17030
+ let totalWeight = 0;
17031
+ let weightedSin = 0;
17032
+ let weightedCos = 0;
17033
+ let weightedWidthScale = 0;
17034
+ let weightedLengthScale = 0;
17035
+ let weightedSwayScale = 0;
17036
+ let weightedDepthScale = 0;
17037
+ let weightedCurlScale = 0;
17038
+ let weightedPrimaryPhase = 0;
17039
+ let weightedSecondaryPhase = 0;
17040
+ for (const tentacleProfile of options.tentacleProfiles) {
17041
+ const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
17042
+ const width = 0.22 * tentacleProfile.widthScale;
17043
+ const weight = Math.exp(-(distance * distance) / (width * width));
17044
+ totalWeight += weight;
17045
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
17046
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
17047
+ weightedWidthScale += tentacleProfile.widthScale * weight;
17048
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
17049
+ weightedSwayScale += tentacleProfile.swayScale * weight;
17050
+ weightedDepthScale += tentacleProfile.depthScale * weight;
17051
+ weightedCurlScale += tentacleProfile.curlScale * weight;
17052
+ weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
17053
+ weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
17054
+ }
17055
+ if (totalWeight < 0.0001) {
17056
+ return {
17057
+ core: 0,
17058
+ centerLongitude: longitude,
17059
+ widthScale: 1,
17060
+ lengthScale: 1,
17061
+ swayScale: 1,
17062
+ depthScale: 1,
17063
+ curlScale: 1,
17064
+ primaryPhase: 0,
17065
+ secondaryPhase: 0,
17066
+ };
17067
+ }
17068
+ return {
17069
+ core: clampNumber$1(totalWeight, 0, 1),
17070
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
17071
+ widthScale: weightedWidthScale / totalWeight,
17072
+ lengthScale: weightedLengthScale / totalWeight,
17073
+ swayScale: weightedSwayScale / totalWeight,
17074
+ depthScale: weightedDepthScale / totalWeight,
17075
+ curlScale: weightedCurlScale / totalWeight,
17076
+ primaryPhase: weightedPrimaryPhase / totalWeight,
17077
+ secondaryPhase: weightedSecondaryPhase / totalWeight,
17078
+ };
17079
+ }
17080
+ /**
17081
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
17082
+ *
17083
+ * @private helper of `octopus3d4AvatarVisual`
17084
+ */
17085
+ function resolveBlobbyLobeWave(options, longitude) {
17086
+ const { morphologyProfile, animationPhase, timeMs } = options;
17087
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
17088
+ animationPhase +
17089
+ timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
17090
+ 1) /
17091
+ 2);
17092
+ }
17093
+ /**
17094
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
17095
+ *
17096
+ * @private helper of `octopus3d4AvatarVisual`
17097
+ */
17098
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
17099
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
17100
+ if (tonalProgress < 0.12) {
17101
+ return palette.highlight;
17102
+ }
17103
+ if (tonalProgress < 0.3) {
17104
+ return palette.secondary;
17105
+ }
17106
+ if (tonalProgress < 0.72) {
17107
+ return forwardness > 0.56 ? palette.secondary : palette.primary;
17108
+ }
17109
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
17110
+ }
17111
+ /**
17112
+ * Draws one projected mesh patch with multi-pass shading and a soft edge.
17113
+ *
17114
+ * @private helper of `octopus3d4AvatarVisual`
17115
+ */
17116
+ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
17117
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
17118
+ if (surfacePatch.lightIntensity > 0) {
17119
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
17120
+ }
17121
+ else if (surfacePatch.lightIntensity < 0) {
17122
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
17123
+ }
17124
+ if (surfacePatch.rimLightIntensity > 0.04) {
17125
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
17126
+ }
17127
+ context.save();
17128
+ context.beginPath();
17129
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
17130
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
17131
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
17132
+ }
17133
+ context.closePath();
17134
+ context.strokeStyle = surfacePatch.outlineColor;
17135
+ context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
17136
+ context.lineJoin = 'round';
17137
+ context.stroke();
17138
+ context.restore();
17139
+ }
17140
+ /**
17141
+ * Draws projected mantle-current lines on the front of the mesh.
17142
+ *
17143
+ * @private helper of `octopus3d4AvatarVisual`
17144
+ */
17145
+ function drawBlobbyContinuousCurrents(options) {
17146
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
17147
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
17148
+ const centerIndex = (currentCount - 1) / 2;
17149
+ context.save();
17150
+ context.lineCap = 'round';
17151
+ context.lineJoin = 'round';
17152
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
17153
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
17154
+ const projectedPoints = [];
17155
+ for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
17156
+ const progress = sampleIndex / 8;
17157
+ const latitude = -0.48 + progress * 0.78;
17158
+ const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
17159
+ const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
17160
+ if (scenePoint.z > center.z - size * 0.016) {
17161
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
17162
+ }
17163
+ }
17164
+ if (projectedPoints.length < 3) {
17165
+ continue;
17166
+ }
17167
+ context.beginPath();
17168
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
17169
+ for (const projectedPoint of projectedPoints.slice(1)) {
17170
+ context.lineTo(projectedPoint.x, projectedPoint.y);
17171
+ }
17172
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
17173
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
17174
+ context.stroke();
17175
+ }
17176
+ context.restore();
17177
+ }
17178
+ /**
17179
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
17180
+ *
17181
+ * @private helper of `octopus3d4AvatarVisual`
17182
+ */
17183
+ function drawBlobbyContinuousSuckers(options) {
17184
+ const { surfaceOptions, size } = options;
17185
+ const { timeMs } = surfaceOptions;
17186
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
17187
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
17188
+ continue;
17189
+ }
17190
+ for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
17191
+ const latitude = 0.5 + suckerIndex * 0.12;
17192
+ const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
17193
+ const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
17194
+ drawBlobbyContinuousSurfaceSpot({
17195
+ ...options,
17196
+ latitude,
17197
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
17198
+ radiusScale: size * (0.0068 - suckerIndex * 0.0006),
17199
+ });
17200
+ }
17201
+ }
17202
+ }
17203
+ /**
17204
+ * Draws seeded pigment spots across the upper mesh for a richer skin texture.
17205
+ *
17206
+ * @private helper of `octopus3d4AvatarVisual`
17207
+ */
17208
+ function drawBlobbySkinSpots(options) {
17209
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
17210
+ for (const skinSpot of skinSpots) {
17211
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
17212
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
17213
+ if (sceneCenterPoint.z <= center.z - size * 0.01) {
17214
+ continue;
17215
+ }
17216
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
17217
+ const spotRadius = size * skinSpot.radiusScale;
17218
+ context.save();
17219
+ context.beginPath();
17220
+ context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
17221
+ context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
17222
+ context.fill();
17223
+ context.restore();
17224
+ }
17225
+ }
17226
+ /**
17227
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
17228
+ *
17229
+ * @private helper of `octopus3d4AvatarVisual`
17230
+ */
17231
+ function drawBlobbyContinuousSurfaceSpot(options) {
17232
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
17233
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
17234
+ const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
17235
+ const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
17236
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
17237
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
17238
+ return;
17239
+ }
17240
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
17241
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
17242
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
17243
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
17244
+ radiusScale *
17245
+ 0.78, size * 0.003, size * 0.02);
17246
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
17247
+ radiusScale *
17248
+ 0.54, size * 0.0024, size * 0.015);
17249
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
17250
+ context.save();
17251
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
17252
+ context.rotate(rotation);
17253
+ context.beginPath();
17254
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
17255
+ context.fillStyle = `${palette.highlight}80`;
17256
+ context.fill();
17257
+ context.strokeStyle = `${palette.highlight}a8`;
17258
+ context.lineWidth = Math.max(0.7, size * 0.0028);
17259
+ context.stroke();
17260
+ context.restore();
17261
+ }
17262
+ /**
17263
+ * Draws a soft, slowly drifting gloss highlight on the front of the mesh.
17264
+ *
17265
+ * @private helper of `octopus3d4AvatarVisual`
17266
+ */
17267
+ function drawBlobbyContinuousGloss(options) {
17268
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
17269
+ const { timeMs, animationPhase } = surfaceOptions;
17270
+ const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
17271
+ const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
17272
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
17273
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
17274
+ if (sceneCenterPoint.z <= center.z) {
17275
+ return;
17276
+ }
17277
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
17278
+ const glossRadius = size * 0.058;
17279
+ context.save();
17280
+ const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
17281
+ glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
17282
+ glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
17283
+ glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
17284
+ context.fillStyle = glossGradient;
17285
+ context.beginPath();
17286
+ context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
17287
+ context.fill();
17288
+ context.restore();
17289
+ }
17290
+ /**
17291
+ * Resolves a signed angular distance from the source longitude to the target longitude.
17292
+ *
17293
+ * @private helper of `octopus3d4AvatarVisual`
17294
+ */
17295
+ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
17296
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
17297
+ }
17298
+ /**
17299
+ * Smoothly maps a value between two bounds into `[0, 1]`.
17300
+ *
17301
+ * @private helper of `octopus3d4AvatarVisual`
17302
+ */
17303
+ function smoothStep(edgeStart, edgeEnd, value) {
17304
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
17305
+ return progress * progress * (3 - 2 * progress);
17306
+ }
17307
+ /**
17308
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
17309
+ *
17310
+ * @private helper of `octopus3d4AvatarVisual`
17311
+ */
17312
+ function formatAlphaHex(opacity) {
17313
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
17314
+ .toString(16)
17315
+ .padStart(2, '0');
17316
+ }
17317
+
15592
17318
  /* eslint-disable no-magic-numbers */
15593
17319
  /**
15594
17320
  * Octopus avatar visual.
@@ -16359,6 +18085,8 @@
16359
18085
  octopus3AvatarVisual,
16360
18086
  octopus3dAvatarVisual,
16361
18087
  octopus3d2AvatarVisual,
18088
+ octopus3d3AvatarVisual,
18089
+ octopus3d4AvatarVisual,
16362
18090
  asciiOctopusAvatarVisual,
16363
18091
  minecraftAvatarVisual,
16364
18092
  minecraft2AvatarVisual,
@@ -17038,6 +18766,70 @@
17038
18766
  }
17039
18767
  // Note: [💞] Ignore a discrepancy between file name and entity name
17040
18768
 
18769
+ /**
18770
+ * META VISIBILITY commitment definition.
18771
+ *
18772
+ * The `META VISIBILITY` commitment stores whether an agent is public, private, or unlisted.
18773
+ * Agents Server mirrors this value into the database for efficient filtering, but the book
18774
+ * commitment remains the editable source of truth.
18775
+ *
18776
+ * @private Metadata-only commitment used by Agents Server.
18777
+ */
18778
+ class MetaVisibilityCommitmentDefinition extends BaseCommitmentDefinition {
18779
+ constructor() {
18780
+ super('META VISIBILITY');
18781
+ }
18782
+ /**
18783
+ * Short one-line description of META VISIBILITY.
18784
+ */
18785
+ get description() {
18786
+ return 'Set whether the agent is private, unlisted, or public.';
18787
+ }
18788
+ /**
18789
+ * Icon for this commitment.
18790
+ */
18791
+ get icon() {
18792
+ return '👁️';
18793
+ }
18794
+ /**
18795
+ * Markdown documentation for META VISIBILITY commitment.
18796
+ */
18797
+ get documentation() {
18798
+ return spacetrim.spaceTrim(`
18799
+ # META VISIBILITY
18800
+
18801
+ Sets the agent visibility used by Agents Server.
18802
+
18803
+ ## Allowed values
18804
+
18805
+ - \`PRIVATE\` - accessible only to signed-in users with access.
18806
+ - \`UNLISTED\` - accessible by direct link but hidden from public listings.
18807
+ - \`PUBLIC\` - visible in public listings and accessible by anyone.
18808
+
18809
+ ## Key aspects
18810
+
18811
+ - Does not modify the agent's behavior, system message, or tools.
18812
+ - Whitespace and letter case are normalized when persisted.
18813
+ - If multiple \`META VISIBILITY\` commitments are present, persistence keeps one normalized value.
18814
+ - Agents Server mirrors the value into the database for filtering, but the book is the source of truth.
18815
+
18816
+ ## Example
18817
+
18818
+ \`\`\`book
18819
+ Helpful Assistant
18820
+
18821
+ GOAL Be helpful and friendly.
18822
+ META VISIBILITY PUBLIC
18823
+ \`\`\`
18824
+ `);
18825
+ }
18826
+ applyToAgentModelRequirements(requirements, content) {
18827
+ // META VISIBILITY is metadata only and does not alter model requirements.
18828
+ return requirements;
18829
+ }
18830
+ }
18831
+ // Note: [💞] Ignore a discrepancy between file name and entity name
18832
+
17041
18833
  /**
17042
18834
  * META VOICE commitment definition
17043
18835
  *
@@ -18647,7 +20439,15 @@
18647
20439
  * Builds the teammate request text, optionally including context.
18648
20440
  */
18649
20441
  function buildTeammateRequest(message, context) {
18650
- return context ? `${message}\n\nContext:\n${context}` : message;
20442
+ if (!context) {
20443
+ return message;
20444
+ }
20445
+ return spacetrim.spaceTrim((block) => `
20446
+ ${block(message)}
20447
+
20448
+ Context:
20449
+ ${block(context)}
20450
+ `);
18651
20451
  }
18652
20452
  /**
18653
20453
  * Builds a minimal chat prompt for teammate calls.
@@ -22473,7 +24273,11 @@
22473
24273
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
22474
24274
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
22475
24275
  const contentToReturn = wasCharacterTruncated
22476
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
24276
+ ? spacetrim.spaceTrim((block) => `
24277
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
24278
+
24279
+ [...truncated...]
24280
+ `)
22477
24281
  : lineRangedContent;
22478
24282
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
22479
24283
  if (wasCharacterTruncated) {
@@ -25549,6 +27353,7 @@
25549
27353
  new MetaDomainCommitmentDefinition(),
25550
27354
  new MetaDisclaimerCommitmentDefinition(),
25551
27355
  new MetaInputPlaceholderCommitmentDefinition(),
27356
+ new MetaVisibilityCommitmentDefinition(),
25552
27357
  new MetaCommitmentDefinition(),
25553
27358
  new MetaVoiceCommitmentDefinition(),
25554
27359
  new NoteCommitmentDefinition('NOTE'),
@@ -26009,11 +27814,19 @@
26009
27814
  try {
26010
27815
  const json = JSON.parse(content);
26011
27816
  const formattedJson = JSON.stringify(json, null, 4);
26012
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
27817
+ return spacetrim.spaceTrim((block) => `
27818
+ \`\`\`json
27819
+ ${block(formattedJson)}
27820
+ \`\`\`
27821
+ `);
26013
27822
  }
26014
27823
  catch (error) {
26015
27824
  // If JSON is invalid, still import it but maybe not as pretty JSON
26016
- return `\`\`\`json\n${content}\n\`\`\``;
27825
+ return spacetrim.spaceTrim((block) => `
27826
+ \`\`\`json
27827
+ ${block(content)}
27828
+ \`\`\`
27829
+ `);
26017
27830
  }
26018
27831
  },
26019
27832
  };
@@ -26037,7 +27850,11 @@
26037
27850
  import(content, mimeType) {
26038
27851
  const extension = mimeTypeToExtension(mimeType);
26039
27852
  const codeBlockType = extension || 'txt';
26040
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
27853
+ return spacetrim.spaceTrim((block) => `
27854
+ \`\`\`${codeBlockType}
27855
+ ${block(content)}
27856
+ \`\`\`
27857
+ `);
26041
27858
  },
26042
27859
  };
26043
27860
 
@@ -26284,11 +28101,19 @@
26284
28101
  const examples = [];
26285
28102
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
26286
28103
  if (initialMessage) {
26287
- examples.push(`**Agent:**\n${initialMessage}`);
28104
+ examples.push(spacetrim.spaceTrim((block) => `
28105
+ **Agent:**
28106
+ ${block(initialMessage)}
28107
+ `));
26288
28108
  }
26289
28109
  if (samples && samples.length > 0) {
26290
28110
  for (const sample of samples) {
26291
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
28111
+ examples.push(spacetrim.spaceTrim((block) => `
28112
+ **User:** ${block(String(sample.question))}
28113
+
28114
+ **Agent:**
28115
+ ${block(sample.answer)}
28116
+ `));
26292
28117
  }
26293
28118
  }
26294
28119
  return examples;
@@ -26873,6 +28698,7 @@
26873
28698
  'META COLOR': applyMetaColorContent,
26874
28699
  'META FONT': applyMetaFontContent,
26875
28700
  'META VOICE': applyMetaVoiceContent,
28701
+ 'META VISIBILITY': applyMetaVisibilityContent,
26876
28702
  };
26877
28703
  /**
26878
28704
  * Applies META-style commitments that mutate parsed profile metadata.
@@ -26902,6 +28728,10 @@
26902
28728
  applyMetaAvatarContent(state, metaValue);
26903
28729
  return;
26904
28730
  }
28731
+ if (metaTypeRaw.toUpperCase() === 'VISIBILITY') {
28732
+ applyMetaVisibilityContent(state, metaValue);
28733
+ return;
28734
+ }
26905
28735
  const metaType = normalizeTo_camelCase(metaTypeRaw);
26906
28736
  state.meta[metaType] = metaValue;
26907
28737
  }
@@ -26978,6 +28808,15 @@
26978
28808
  function applyMetaVoiceContent(state, content) {
26979
28809
  state.meta.voice = spacetrim.spaceTrim(content);
26980
28810
  }
28811
+ /**
28812
+ * Applies META VISIBILITY content into the normalized `meta.visibility` field.
28813
+ */
28814
+ function applyMetaVisibilityContent(state, content) {
28815
+ const visibility = normalizeAgentVisibility(content);
28816
+ if (visibility) {
28817
+ state.meta.visibility = visibility;
28818
+ }
28819
+ }
26981
28820
  /**
26982
28821
  * Normalizes the separator in the content
26983
28822
  *
@@ -28003,7 +29842,11 @@
28003
29842
  * @private internal utility of `createStandaloneBookLanguageMarkdown`
28004
29843
  */
28005
29844
  function renderDocumentationSection(title, documentation) {
28006
- return `#### ${title}\n\n${removeLeadingTopLevelHeading(documentation)}`;
29845
+ return spacetrim.spaceTrim((block) => `
29846
+ #### ${title}
29847
+
29848
+ ${block(removeLeadingTopLevelHeading(documentation))}
29849
+ `);
28007
29850
  }
28008
29851
  /**
28009
29852
  * Renders the documentation body for one grouped commitment entry.
@@ -28079,6 +29922,7 @@
28079
29922
  'META DESCRIPTION',
28080
29923
  'META DISCLAIMER',
28081
29924
  'META INPUT PLACEHOLDER',
29925
+ 'META VISIBILITY',
28082
29926
  'META COLOR',
28083
29927
  'META FONT',
28084
29928
  'META VOICE',
@@ -28654,7 +30498,9 @@
28654
30498
  * @private internal utility of `Book`
28655
30499
  */
28656
30500
  function parseCommitmentHeader(line) {
28657
- const match = /^([A-Z][A-Z0-9]*(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
30501
+ // Require at least 2 characters in the first keyword word to avoid treating common
30502
+ // single-letter words (e.g. "V" in Czech, "I" or "A" in English) as commitment headers.
30503
+ const match = /^([A-Z][A-Z0-9]+(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
28658
30504
  if (!match) {
28659
30505
  return null;
28660
30506
  }
@@ -28752,18 +30598,23 @@
28752
30598
  *
28753
30599
  * @private shared persistence helper for `AgentCollectionInSupabase`
28754
30600
  */
28755
- function prepareAgentSourceForPersistence(agentSource) {
28756
- let agentProfile = parseAgentSource(agentSource);
28757
- const permanentId = agentProfile.permanentId;
28758
- const strippedAgentSource = stripMetaIdLines(agentSource);
28759
- if (strippedAgentSource !== agentSource) {
28760
- agentSource = strippedAgentSource;
28761
- agentProfile = parseAgentSource(agentSource);
28762
- }
30601
+ function prepareAgentSourceForPersistence(agentSource, options = {}) {
30602
+ var _a, _b, _c;
30603
+ const originalAgentProfile = parseAgentSource(agentSource);
30604
+ const permanentId = originalAgentProfile.permanentId;
30605
+ agentSource = stripMetaIdLines(agentSource);
30606
+ const sourceVisibility = parseAgentSourceVisibility(agentSource, { isStrict: true });
30607
+ const resolvedVisibility = options.isVisibilityOverride || !sourceVisibility
30608
+ ? (_b = (_a = options.visibility) !== null && _a !== void 0 ? _a : sourceVisibility) !== null && _b !== void 0 ? _b : DEFAULT_AGENT_VISIBILITY
30609
+ : sourceVisibility;
30610
+ agentSource = setAgentSourceVisibility(agentSource, resolvedVisibility);
30611
+ const agentProfile = parseAgentSource(agentSource);
30612
+ const visibility = (_c = parseAgentSourceVisibility(agentSource, { isStrict: true })) !== null && _c !== void 0 ? _c : undefined;
28763
30613
  return {
28764
30614
  agentProfile,
28765
30615
  agentSource,
28766
30616
  permanentId,
30617
+ visibility,
28767
30618
  };
28768
30619
  }
28769
30620
  /**
@@ -28794,8 +30645,11 @@
28794
30645
  * @private shared persistence helper for `AgentCollectionInSupabase`
28795
30646
  */
28796
30647
  function createAgentPersistenceRecords(agentSource, options = {}, createdAt = new Date().toISOString()) {
28797
- const preparedAgentSource = prepareAgentSourceForPersistence(agentSource);
28798
- const { agentProfile, agentSource: normalizedAgentSource } = preparedAgentSource;
30648
+ const preparedAgentSource = prepareAgentSourceForPersistence(agentSource, {
30649
+ visibility: options.visibility,
30650
+ isVisibilityOverride: options.visibility !== undefined,
30651
+ });
30652
+ const { agentProfile, agentSource: normalizedAgentSource, visibility } = preparedAgentSource;
28799
30653
  const permanentId = preparedAgentSource.permanentId || $randomBase58(14);
28800
30654
  const { agentName, agentHash } = agentProfile;
28801
30655
  const agentInsertRecord = {
@@ -28815,8 +30669,8 @@
28815
30669
  if (options.sortOrder !== undefined) {
28816
30670
  agentInsertRecord.sortOrder = options.sortOrder;
28817
30671
  }
28818
- if (options.visibility !== undefined) {
28819
- agentInsertRecord.visibility = options.visibility;
30672
+ if (visibility !== undefined) {
30673
+ agentInsertRecord.visibility = visibility;
28820
30674
  }
28821
30675
  return {
28822
30676
  createdAgent: {
@@ -28850,6 +30704,18 @@
28850
30704
  const normalizedVersionName = versionName.trim();
28851
30705
  return normalizedVersionName.length > 0 ? normalizedVersionName : null;
28852
30706
  }
30707
+ /**
30708
+ * Builds a Supabase `.or()` filter for agent name or permanent id lookups.
30709
+ *
30710
+ * @param agentNameOrPermanentId - Agent name or stable permanent identifier to match.
30711
+ * @returns `.or()` filter string safe to pass to Supabase.
30712
+ *
30713
+ * @private internal helper of `AgentCollectionInSupabase`
30714
+ */
30715
+ function buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId) {
30716
+ const encodedAgentIdentifier = encodeURIComponent(agentNameOrPermanentId);
30717
+ return `agentName.eq.${encodedAgentIdentifier},permanentId.eq.${encodedAgentIdentifier}`;
30718
+ }
28853
30719
  /**
28854
30720
  * Agent collection stored in a Supabase table.
28855
30721
  *
@@ -28897,21 +30763,63 @@
28897
30763
  if (isVerbose) {
28898
30764
  console.info(`Found ${selectResult.data.length} agents in directory`);
28899
30765
  }
28900
- return selectResult.data.map(({ agentName, agentProfile, permanentId }) => {
28901
- if (isVerbose && agentProfile.agentName !== agentName) {
28902
- console.warn(spaceTrim(`
30766
+ return selectResult.data.map((row) => this.mapAgentBasicInformationRow(row, isVerbose));
30767
+ }
30768
+ /**
30769
+ * Finds one active agent profile by its human-readable name or permanent id.
30770
+ *
30771
+ * This keeps route-level lookups from loading the whole agent collection when only
30772
+ * one canonical route identifier is needed.
30773
+ *
30774
+ * @param agentNameOrPermanentId - Agent name or stable permanent identifier.
30775
+ * @returns Matching active agent profile or `null` when not found.
30776
+ *
30777
+ * @public exported from `@promptbook/core`
30778
+ */
30779
+ async findAgentBasicInformation(agentNameOrPermanentId) {
30780
+ var _a;
30781
+ const { isVerbose = exports.DEFAULT_IS_VERBOSE } = this.options || {};
30782
+ const selectResult = await this.supabaseClient
30783
+ .from(this.getTableName('Agent'))
30784
+ .select('agentName,agentProfile,permanentId')
30785
+ .or(buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId))
30786
+ .is('deletedAt', null)
30787
+ .order('createdAt', { ascending: true })
30788
+ .limit(1);
30789
+ if (selectResult.error) {
30790
+ throw new DatabaseError(spaceTrim((block) => `
30791
+
30792
+ Error fetching agent "${agentNameOrPermanentId}" from Supabase:
30793
+
30794
+ ${block(selectResult.error.message)}
30795
+ `));
30796
+ }
30797
+ const row = (_a = selectResult.data) === null || _a === void 0 ? void 0 : _a[0];
30798
+ return row ? this.mapAgentBasicInformationRow(row, isVerbose) : null;
30799
+ }
30800
+ /**
30801
+ * Converts one database row into public agent information.
30802
+ *
30803
+ * @param row - Database row carrying the persisted profile snapshot.
30804
+ * @param isVerbose - Whether profile-name mismatches should be logged.
30805
+ * @returns Agent profile with canonical database name and permanent id.
30806
+ *
30807
+ * @private internal helper of `AgentCollectionInSupabase`
30808
+ */
30809
+ mapAgentBasicInformationRow({ agentName, agentProfile, permanentId }, isVerbose) {
30810
+ if (isVerbose && agentProfile.agentName !== agentName) {
30811
+ console.warn(spaceTrim(`
28903
30812
  Agent name mismatch for agent "${agentName}". Using name from database.
28904
30813
 
28905
30814
  agentName: "${agentName}"
28906
30815
  agentProfile.agentName: "${agentProfile.agentName}"
28907
30816
  `));
28908
- }
28909
- return {
28910
- ...agentProfile,
28911
- agentName,
28912
- permanentId: permanentId || agentProfile.permanentId,
28913
- };
28914
- });
30817
+ }
30818
+ return {
30819
+ ...agentProfile,
30820
+ agentName,
30821
+ permanentId: permanentId || agentProfile.permanentId,
30822
+ };
28915
30823
  }
28916
30824
  /**
28917
30825
  * Retrieves the permanent ID of an agent by its name or permanent ID.
@@ -28920,7 +30828,7 @@
28920
30828
  const selectResult = await this.supabaseClient
28921
30829
  .from(this.getTableName('Agent'))
28922
30830
  .select('permanentId')
28923
- .or(`agentName.eq.${agentNameOrPermanentId},permanentId.eq.${agentNameOrPermanentId}`)
30831
+ .or(buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId))
28924
30832
  .order('createdAt', { ascending: true }) // Pick oldest if multiple match by name
28925
30833
  .limit(1);
28926
30834
  if (selectResult.error || !selectResult.data || selectResult.data.length === 0) {
@@ -28935,7 +30843,7 @@
28935
30843
  const selectResult = await this.supabaseClient
28936
30844
  .from(this.getTableName('Agent'))
28937
30845
  .select('agentSource')
28938
- .or(`agentName.eq.${agentNameOrPermanentId},permanentId.eq.${agentNameOrPermanentId}`)
30846
+ .or(buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId))
28939
30847
  .is('deletedAt', null)
28940
30848
  .order('createdAt', { ascending: true }) // Pick oldest if multiple match by name
28941
30849
  .limit(1);
@@ -28979,7 +30887,7 @@
28979
30887
  async updateAgentSource(permanentId, agentSource, options = {}) {
28980
30888
  const selectPreviousAgentResult = await this.supabaseClient
28981
30889
  .from(this.getTableName('Agent'))
28982
- .select('agentHash,agentName,permanentId')
30890
+ .select('agentHash,agentName,permanentId,visibility')
28983
30891
  .eq('permanentId', permanentId)
28984
30892
  .single();
28985
30893
  if (selectPreviousAgentResult.error) {
@@ -28994,8 +30902,11 @@
28994
30902
  selectPreviousAgentResult.data.agentName;
28995
30903
  const previousAgentHash = selectPreviousAgentResult.data.agentHash;
28996
30904
  const previousPermanentId = selectPreviousAgentResult.data.permanentId;
28997
- const preparedAgentSource = prepareAgentSourceForPersistence(agentSource);
28998
- const { agentProfile, agentSource: normalizedAgentSource } = preparedAgentSource;
30905
+ const previousVisibility = selectPreviousAgentResult.data.visibility;
30906
+ const preparedAgentSource = prepareAgentSourceForPersistence(agentSource, {
30907
+ visibility: previousVisibility,
30908
+ });
30909
+ const { agentProfile, agentSource: normalizedAgentSource, visibility } = preparedAgentSource;
28999
30910
  let { permanentId: newPermanentId } = preparedAgentSource;
29000
30911
  const { agentHash, agentName } = agentProfile;
29001
30912
  if (!newPermanentId && previousPermanentId) {
@@ -29017,6 +30928,7 @@
29017
30928
  updatedAt: new Date().toISOString(),
29018
30929
  agentHash: agentProfile.agentHash,
29019
30930
  agentSource: normalizedAgentSource,
30931
+ visibility: visibility !== null && visibility !== void 0 ? visibility : previousVisibility,
29020
30932
  promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
29021
30933
  })
29022
30934
  .eq('permanentId', permanentId);
@@ -34416,7 +36328,8 @@
34416
36328
  * @private internal function of `$registeredLlmToolsMessage`
34417
36329
  */
34418
36330
  function createUsedEnvMessage() {
34419
- return `Unknown \`.env\` file` ;
36331
+ return `Unknown \`.env\` file`
36332
+ ;
34420
36333
  }
34421
36334
  // TODO: [®] DRY Register logic
34422
36335
  // TODO: [🧠][⚛] Maybe pass env as argument
@@ -35729,7 +37642,9 @@
35729
37642
  if (!executionTools || !executionTools.script) {
35730
37643
  throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
35731
37644
  }
35732
- return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
37645
+ return Array.isArray(executionTools.script)
37646
+ ? executionTools.script
37647
+ : [executionTools.script];
35733
37648
  }
35734
37649
  /**
35735
37650
  * Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
@@ -38791,7 +40706,7 @@
38791
40706
  return { skippedReason: 'invalid_data_url' };
38792
40707
  }
38793
40708
  return {
38794
- file: new File([parsed.buffer], parsed.filename, {
40709
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
38795
40710
  type: parsed.mimeType,
38796
40711
  }),
38797
40712
  sizeBytes: parsed.buffer.length,
@@ -40554,7 +42469,7 @@
40554
42469
  * Computes one stable hash from a JSON-serializable value.
40555
42470
  */
40556
42471
  function computeJsonHash$1(value) {
40557
- return cryptoJs.SHA256(JSON.stringify(value)).toString();
42472
+ return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
40558
42473
  }
40559
42474
  /**
40560
42475
  * Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
@@ -40712,7 +42627,7 @@
40712
42627
  * Computes one stable hash from a JSON-serializable value.
40713
42628
  */
40714
42629
  function computeJsonHash(value) {
40715
- return cryptoJs.SHA256(JSON.stringify(value)).toString();
42630
+ return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
40716
42631
  }
40717
42632
  /**
40718
42633
  * Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
@@ -41102,7 +43017,11 @@
41102
43017
  };
41103
43018
  }
41104
43019
  return {
41105
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
43020
+ content: spacetrim.spaceTrim((block) => `
43021
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
43022
+
43023
+ [...truncated...]
43024
+ `),
41106
43025
  isTruncated: true,
41107
43026
  };
41108
43027
  }
@@ -41568,7 +43487,7 @@
41568
43487
  * Returns a virtual model name representing the agent behavior.
41569
43488
  */
41570
43489
  get modelName() {
41571
- const hash = cryptoJs.SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
43490
+ const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
41572
43491
  const agentId = hash.substring(0, 10);
41573
43492
  return (normalizeToKebabCase(this.title) + '-' + agentId);
41574
43493
  }
@@ -45279,7 +47198,7 @@
45279
47198
  title: 'Anthropic Claude',
45280
47199
  packageName: '@promptbook/anthropic-claude',
45281
47200
  className: 'AnthropicClaudeExecutionTools',
45282
- envVariables: ['ANTHROPIC_CLAUDE_API_KEY'],
47201
+ envVariables: ['ANTHROPIC_API_KEY', 'ANTHROPIC_CLAUDE_API_KEY'],
45283
47202
  trustLevel: 'CLOSED',
45284
47203
  order: MODEL_ORDERS.TOP_TIER,
45285
47204
  getBoilerplateConfiguration() {
@@ -45296,14 +47215,17 @@
45296
47215
  };
45297
47216
  },
45298
47217
  createConfigurationFromEnv(env) {
47218
+ var _a;
45299
47219
  // Note: Note using `process.env` BUT `env` to pass in the environment variables dynamically
45300
- if (typeof env.ANTHROPIC_CLAUDE_API_KEY === 'string') {
47220
+ // Note: `ANTHROPIC_API_KEY` takes precedence over the deprecated `ANTHROPIC_CLAUDE_API_KEY`
47221
+ const apiKey = (_a = env.ANTHROPIC_API_KEY) !== null && _a !== void 0 ? _a : env.ANTHROPIC_CLAUDE_API_KEY;
47222
+ if (typeof apiKey === 'string') {
45301
47223
  return {
45302
47224
  title: 'Claude (from env)',
45303
47225
  packageName: '@promptbook/anthropic-claude',
45304
47226
  className: 'AnthropicClaudeExecutionTools',
45305
47227
  options: {
45306
- apiKey: env.ANTHROPIC_CLAUDE_API_KEY,
47228
+ apiKey,
45307
47229
  },
45308
47230
  };
45309
47231
  }
@@ -48714,20 +50636,7 @@
48714
50636
  }
48715
50637
 
48716
50638
  /**
48717
- * Core Promptbook server configuration
48718
- *
48719
- * Used for "Adam" agent which is built in as default ancestor for new agents and other well known agents
48720
- *
48721
- * @public exported from `@promptbook/core`
48722
- */
48723
- const CORE_AGENTS_SERVER = {
48724
- title: 'Promptbook Core',
48725
- description: `Core Promptbook server used for Adam agent which is built in as default ancestor for new agents and other well known agents.`,
48726
- owner: PROMPTBOOK_LEGAL_ENTITY,
48727
- url: 'https://core.ptbk.io/',
48728
- };
48729
- /**
48730
- * Names of well known agents hosted on Core Promptbook server
50639
+ * Names of well known agents bundled in the local `.core` folder of every Agents Server
48731
50640
  *
48732
50641
  * - `Adam`: The default ancestor agent for new agents
48733
50642
  * - `Teacher`: Agent that knows book syntax and can help with self-learning
@@ -48753,7 +50662,6 @@
48753
50662
  * @public exported from `@promptbook/core`
48754
50663
  */
48755
50664
  const PUBLIC_AGENTS_SERVERS = [
48756
- CORE_AGENTS_SERVER,
48757
50665
  {
48758
50666
  title: 'Promptbook Gallery',
48759
50667
  description: `Gallery of ideas, AI professions,... like AI Agenta photobank.`,
@@ -49211,6 +51119,7 @@
49211
51119
  exports.$scrapersRegister = $scrapersRegister;
49212
51120
  exports.ADMIN_EMAIL = ADMIN_EMAIL;
49213
51121
  exports.ADMIN_GITHUB_NAME = ADMIN_GITHUB_NAME;
51122
+ exports.AGENT_VISIBILITY_VALUES = AGENT_VISIBILITY_VALUES;
49214
51123
  exports.API_REQUEST_TIMEOUT = API_REQUEST_TIMEOUT;
49215
51124
  exports.AbstractFormatError = AbstractFormatError;
49216
51125
  exports.Agent = Agent;
@@ -49229,7 +51138,6 @@
49229
51138
  exports.CLAIM = CLAIM;
49230
51139
  exports.CLI_APP_ID = CLI_APP_ID;
49231
51140
  exports.COLOR_CONSTANTS = COLOR_CONSTANTS;
49232
- exports.CORE_AGENTS_SERVER = CORE_AGENTS_SERVER;
49233
51141
  exports.CORE_AGENTS_SERVER_WELL_KNOWN_AGENT_NAMES = CORE_AGENTS_SERVER_WELL_KNOWN_AGENT_NAMES;
49234
51142
  exports.CallbackInterfaceTools = CallbackInterfaceTools;
49235
51143
  exports.ChatbotFormfactorDefinition = ChatbotFormfactorDefinition;
@@ -49239,6 +51147,7 @@
49239
51147
  exports.CsvFormatError = CsvFormatError;
49240
51148
  exports.CsvFormatParser = CsvFormatParser;
49241
51149
  exports.DEFAULT_AGENTS_DIRNAME = DEFAULT_AGENTS_DIRNAME;
51150
+ exports.DEFAULT_AGENT_VISIBILITY = DEFAULT_AGENT_VISIBILITY;
49242
51151
  exports.DEFAULT_BOOK = DEFAULT_BOOK;
49243
51152
  exports.DEFAULT_BOOKS_DIRNAME = DEFAULT_BOOKS_DIRNAME;
49244
51153
  exports.DEFAULT_BOOK_OUTPUT_PARAMETER_NAME = DEFAULT_BOOK_OUTPUT_PARAMETER_NAME;
@@ -49385,14 +51294,17 @@
49385
51294
  exports.getAllCommitmentsToolTitles = getAllCommitmentsToolTitles;
49386
51295
  exports.getCommitmentDefinition = getCommitmentDefinition;
49387
51296
  exports.getGroupedCommitmentDefinitions = getGroupedCommitmentDefinitions;
51297
+ exports.getNextAgentVisibility = getNextAgentVisibility;
49388
51298
  exports.getPipelineInterface = getPipelineInterface;
49389
51299
  exports.getSingleLlmExecutionTools = getSingleLlmExecutionTools;
49390
51300
  exports.identificationToPromptbookToken = identificationToPromptbookToken;
51301
+ exports.isAgentVisibility = isAgentVisibility;
49391
51302
  exports.isCommitmentSupported = isCommitmentSupported;
49392
51303
  exports.isPassingExpectations = isPassingExpectations;
49393
51304
  exports.isPipelineImplementingInterface = isPipelineImplementingInterface;
49394
51305
  exports.isPipelineInterfacesEqual = isPipelineInterfacesEqual;
49395
51306
  exports.isPipelinePrepared = isPipelinePrepared;
51307
+ exports.isPublicAgentVisibility = isPublicAgentVisibility;
49396
51308
  exports.isValidBook = isValidBook;
49397
51309
  exports.isValidPipelineString = isValidPipelineString;
49398
51310
  exports.joinLlmExecutionTools = joinLlmExecutionTools;
@@ -49400,9 +51312,13 @@
49400
51312
  exports.makeKnowledgeSourceHandler = makeKnowledgeSourceHandler;
49401
51313
  exports.migratePipeline = migratePipeline;
49402
51314
  exports.normalizeAgentName = normalizeAgentName;
51315
+ exports.normalizeAgentVisibility = normalizeAgentVisibility;
49403
51316
  exports.normalizeChatAttachments = normalizeChatAttachments;
49404
51317
  exports.padBook = padBook;
49405
51318
  exports.parseAgentSource = parseAgentSource;
51319
+ exports.parseAgentSourceVisibility = parseAgentSourceVisibility;
51320
+ exports.parseAgentVisibility = parseAgentVisibility;
51321
+ exports.parseAgentVisibilityStrict = parseAgentVisibilityStrict;
49406
51322
  exports.parseParameters = parseParameters;
49407
51323
  exports.parsePipeline = parsePipeline;
49408
51324
  exports.pipelineCollectionToJson = pipelineCollectionToJson;
@@ -49414,6 +51330,7 @@
49414
51330
  exports.promptbookFetch = promptbookFetch;
49415
51331
  exports.promptbookTokenToIdentification = promptbookTokenToIdentification;
49416
51332
  exports.resolveChatAttachmentContents = resolveChatAttachmentContents;
51333
+ exports.setAgentSourceVisibility = setAgentSourceVisibility;
49417
51334
  exports.unpreparePipeline = unpreparePipeline;
49418
51335
  exports.usageToHuman = usageToHuman;
49419
51336
  exports.usageToWorktime = usageToWorktime;