@promptbook/cli 0.112.0-134 → 0.112.0-136

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 (94) hide show
  1. package/apps/agents-server/README.md +0 -1
  2. package/apps/agents-server/src/app/admin/chat-feedback/page.tsx +2 -4
  3. package/apps/agents-server/src/app/admin/chat-history/page.tsx +2 -4
  4. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +246 -15
  5. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +237 -4
  6. package/apps/agents-server/src/app/api/admin/update/log/route.ts +42 -0
  7. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +28 -9
  8. package/apps/agents-server/src/app/api/agents/export/route.ts +41 -0
  9. package/apps/agents-server/src/app/api/agents/import/route.ts +168 -0
  10. package/apps/agents-server/src/app/api/chat/route.ts +29 -7
  11. package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +2 -2
  12. package/apps/agents-server/src/app/api/chat-feedback/route.ts +3 -3
  13. package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +2 -2
  14. package/apps/agents-server/src/app/api/chat-history/export/route.ts +2 -2
  15. package/apps/agents-server/src/app/api/chat-history/route.ts +3 -3
  16. package/apps/agents-server/src/app/api/chat-streaming/route.ts +29 -0
  17. package/apps/agents-server/src/app/api/elevenlabs/tts/route.ts +60 -2
  18. package/apps/agents-server/src/app/api/federated-agents/route.ts +1 -1
  19. package/apps/agents-server/src/app/api/images/[filename]/route.ts +102 -0
  20. package/apps/agents-server/src/app/api/metadata/export/route.ts +34 -0
  21. package/apps/agents-server/src/app/api/metadata/import/route.ts +46 -0
  22. package/apps/agents-server/src/app/api/openai/v1/audio/transcriptions/route.ts +111 -2
  23. package/apps/agents-server/src/app/api/page-preview/check/route.ts +18 -0
  24. package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +15 -8
  25. package/apps/agents-server/src/app/api/team-agent-profile/route.ts +20 -0
  26. package/apps/agents-server/src/app/layout.tsx +1 -1
  27. package/apps/agents-server/src/components/AgentContextMenu/useAgentContextMenuItems.ts +27 -19
  28. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +31 -1
  29. package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +60 -1
  30. package/apps/agents-server/src/components/Homepage/useAgentsListImportExportState.ts +523 -0
  31. package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +16 -0
  32. package/apps/agents-server/src/constants/defaultAgentAvatarVisual.ts +1 -1
  33. package/apps/agents-server/src/database/metadataDefaults.ts +0 -21
  34. package/apps/agents-server/src/database/migrations/2026-06-2700-default-agent-avatar-visual-octopus3d4.sql +16 -0
  35. package/apps/agents-server/src/database/seedCoreAgents.ts +235 -0
  36. package/apps/agents-server/src/database/seedDefaultAgents.ts +32 -1
  37. package/apps/agents-server/src/search/createDefaultServerSearchProviders/createFederatedAgentsSearchProvider.ts +1 -1
  38. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +2 -9
  39. package/apps/agents-server/src/utils/agentOwnership.ts +54 -5
  40. package/apps/agents-server/src/utils/agentsTransfer/createAgentsExportZipStream.ts +68 -0
  41. package/apps/agents-server/src/utils/agentsTransfer/importAgentsFromFiles.ts +852 -0
  42. package/apps/agents-server/src/utils/backup/createBooksBackupZipStream.ts +15 -2
  43. package/apps/agents-server/src/utils/defaultAgents/loadDefaultAgentBooks.ts +46 -4
  44. package/apps/agents-server/src/utils/findAgentForCallerWriteAccess.ts +36 -0
  45. package/apps/agents-server/src/utils/getFederatedServers.ts +3 -74
  46. package/apps/agents-server/src/utils/getWellKnownAgentUrl.ts +10 -4
  47. package/apps/agents-server/src/utils/metadataConfigurationTransfer.ts +426 -0
  48. package/apps/agents-server/src/utils/paidApiRequestGuard.ts +241 -0
  49. package/apps/agents-server/src/utils/serverManagement/createManagedServer/bootstrapManagedServer.ts +2 -5
  50. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +162 -0
  51. package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +36 -8
  52. package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +11 -2
  53. package/apps/agents-server/src/utils/userChat/resolveUserChatProgressToolHighlights.ts +100 -0
  54. package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +4 -3
  55. package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +272 -27
  56. package/apps/agents-server/src/utils/validateApiKey.ts +7 -3
  57. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +110 -0
  58. package/esm/index.es.js +795 -39
  59. package/esm/index.es.js.map +1 -1
  60. package/esm/src/_packages/core.index.d.ts +0 -2
  61. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  62. package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  63. package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  64. package/esm/src/version.d.ts +1 -1
  65. package/package.json +1 -1
  66. package/servers.ts +1 -16
  67. package/src/_packages/core.index.ts +0 -2
  68. package/src/avatars/types/AvatarVisualDefinition.ts +1 -0
  69. package/src/avatars/visuals/avatarVisualRegistry.ts +2 -0
  70. package/src/avatars/visuals/octopus3d4AvatarVisual.ts +1295 -0
  71. package/src/other/templates/getTemplatesPipelineCollection.ts +762 -883
  72. package/src/utils/agents/resolveAgentAvatarImageUrl.ts +1 -1
  73. package/src/utils/validators/url/isValidAgentUrl.ts +5 -7
  74. package/src/version.ts +2 -2
  75. package/src/versions.txt +2 -1
  76. package/umd/index.umd.js +795 -39
  77. package/umd/index.umd.js.map +1 -1
  78. package/umd/src/_packages/core.index.d.ts +0 -2
  79. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  80. package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  81. package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  82. package/umd/src/version.d.ts +1 -1
  83. package/apps/agents-server/src/utils/defaultFederatedAgents/DefaultFederatedAgentsSyncOptions.ts +0 -9
  84. package/apps/agents-server/src/utils/defaultFederatedAgents/ensureDefaultFederatedAgentExists.ts +0 -277
  85. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchCoreOrganizationPayload.ts +0 -39
  86. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchFederatedAgentBook.ts +0 -43
  87. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchWithDefaultFederatedAgentTimeout.ts +0 -28
  88. package/apps/agents-server/src/utils/defaultFederatedAgents/getDefaultFederatedAgentSyncPool.ts +0 -38
  89. package/apps/agents-server/src/utils/defaultFederatedAgents/loadActiveLocalAgentIdsByNormalizedName.ts +0 -41
  90. package/apps/agents-server/src/utils/defaultFederatedAgents/loadDefaultFederatedAgentSyncMetadata.ts +0 -76
  91. package/apps/agents-server/src/utils/defaultFederatedAgents/quoteIdentifier.ts +0 -11
  92. package/apps/agents-server/src/utils/defaultFederatedAgents/scheduleDefaultFederatedAgentsSync.ts +0 -88
  93. package/apps/agents-server/src/utils/defaultFederatedAgents/selectDefaultFederatedAgentsFromOrganizationPayload.ts +0 -181
  94. package/apps/agents-server/src/utils/defaultFederatedAgents/synchronizeDefaultFederatedAgents.ts +0 -77
package/umd/index.umd.js CHANGED
@@ -63,7 +63,7 @@
63
63
  * @generated
64
64
  * @see https://github.com/webgptorg/promptbook
65
65
  */
66
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-134';
66
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-136';
67
67
  /**
68
68
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
69
69
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -7739,6 +7739,11 @@
7739
7739
  * - `isValidAgentUrl` *(this one)* which tests just agent URL
7740
7740
  * - `isValidPipelineUrl` which tests just pipeline URL
7741
7741
  *
7742
+ * Note: This is a pure structural validator and does not block private/internal network
7743
+ * addresses. Callers that fetch the URL server-side from an untrusted network context must
7744
+ * additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
7745
+ * outbound request.
7746
+ *
7742
7747
  * @public exported from `@promptbook/utils`
7743
7748
  */
7744
7749
  function isValidAgentUrl(url) {
@@ -7752,12 +7757,6 @@
7752
7757
  // TODO: [🐠]
7753
7758
  return false;
7754
7759
  }
7755
- /*
7756
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
7757
- if (isUrlOnPrivateNetwork(url)) {
7758
- return false;
7759
- }
7760
- */
7761
7760
  return true;
7762
7761
  }
7763
7762
  // TODO: [🐠] Maybe more info why the URL is invalid
@@ -11406,7 +11405,7 @@
11406
11405
  *
11407
11406
  * @private helper of `minecraft2AvatarVisual`
11408
11407
  */
11409
- const LIGHT_DIRECTION$3 = normalizeVector3({
11408
+ const LIGHT_DIRECTION$4 = normalizeVector3({
11410
11409
  x: 0.4,
11411
11410
  y: -0.65,
11412
11411
  z: 0.92,
@@ -11630,7 +11629,7 @@
11630
11629
  corners: projectedCorners,
11631
11630
  texture: faceDefinition.texture,
11632
11631
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
11633
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$3), -1, 1),
11632
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
11634
11633
  outlineColor: cuboid.outlineColor,
11635
11634
  };
11636
11635
  });
@@ -12317,7 +12316,7 @@
12317
12316
  *
12318
12317
  * @private helper of `octopus3AvatarVisual`
12319
12318
  */
12320
- function formatAlphaHex$1(opacity) {
12319
+ function formatAlphaHex$2(opacity) {
12321
12320
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
12322
12321
  .toString(16)
12323
12322
  .padStart(2, '0');
@@ -12696,7 +12695,7 @@
12696
12695
  context.beginPath();
12697
12696
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
12698
12697
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
12699
- context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
12698
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
12700
12699
  context.lineWidth = radiusX * 0.08;
12701
12700
  context.lineCap = 'round';
12702
12701
  context.stroke();
@@ -12777,7 +12776,7 @@
12777
12776
  context.beginPath();
12778
12777
  context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
12779
12778
  context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
12780
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
12779
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
12781
12780
  context.lineWidth = projectedRadiusX * 0.08;
12782
12781
  context.lineCap = 'round';
12783
12782
  context.stroke();
@@ -12823,7 +12822,7 @@
12823
12822
  *
12824
12823
  * @private helper of the 3D octopus avatar visuals
12825
12824
  */
12826
- function formatAlphaHex(opacity) {
12825
+ function formatAlphaHex$1(opacity) {
12827
12826
  return Math.round(clampNumber$1(opacity, 0, 1) * 255)
12828
12827
  .toString(16)
12829
12828
  .padStart(2, '0');
@@ -12835,7 +12834,7 @@
12835
12834
  *
12836
12835
  * @private helper of `octopus3dAvatarVisual`
12837
12836
  */
12838
- const LIGHT_DIRECTION$2 = normalizeVector3({
12837
+ const LIGHT_DIRECTION$3 = normalizeVector3({
12839
12838
  x: 0.48,
12840
12839
  y: -0.62,
12841
12840
  z: 0.94,
@@ -13085,7 +13084,7 @@
13085
13084
  corners: projectedCorners,
13086
13085
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
13087
13086
  transformedCorners.length,
13088
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
13087
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
13089
13088
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
13090
13089
  outlineColor,
13091
13090
  });
@@ -13274,7 +13273,7 @@
13274
13273
  *
13275
13274
  * @private helper of `octopus3d2AvatarVisual`
13276
13275
  */
13277
- const LIGHT_DIRECTION$1 = normalizeVector3({
13276
+ const LIGHT_DIRECTION$2 = normalizeVector3({
13278
13277
  x: 0.38,
13279
13278
  y: -0.6,
13280
13279
  z: 0.98,
@@ -13433,13 +13432,13 @@
13433
13432
  *
13434
13433
  * @private helper of `octopus3d2AvatarVisual`
13435
13434
  */
13436
- const LATITUDE_PATCH_COUNT$1 = 12;
13435
+ const LATITUDE_PATCH_COUNT$2 = 12;
13437
13436
  /**
13438
13437
  * Number of longitude segments used by the single blobby octopus mesh.
13439
13438
  *
13440
13439
  * @private helper of `octopus3d2AvatarVisual`
13441
13440
  */
13442
- const LONGITUDE_PATCH_COUNT$1 = 24;
13441
+ const LONGITUDE_PATCH_COUNT$2 = 24;
13443
13442
  /**
13444
13443
  * Resolves all visible projected patches for the single blobby octopus mesh.
13445
13444
  *
@@ -13453,8 +13452,8 @@
13453
13452
  */
13454
13453
  function resolveVisibleBlobbyOctopusPatches(options) {
13455
13454
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
13456
- const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
13457
- const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
13455
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
13456
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
13458
13457
  const surfacePatches = [];
13459
13458
  const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
13460
13459
  const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
@@ -13513,8 +13512,8 @@
13513
13512
  transformedCorners[2].z +
13514
13513
  transformedCorners[3].z) /
13515
13514
  4,
13516
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
13517
- fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
13515
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
13516
+ fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
13518
13517
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
13519
13518
  });
13520
13519
  }
@@ -13582,7 +13581,7 @@
13582
13581
  *
13583
13582
  * @private helper of `octopus3d2AvatarVisual`
13584
13583
  */
13585
- function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
13584
+ function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
13586
13585
  const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
13587
13586
  if (tonalProgress < 0.16) {
13588
13587
  return palette.highlight;
@@ -13628,7 +13627,7 @@
13628
13627
  *
13629
13628
  * @private helper of `octopus3d3AvatarVisual`
13630
13629
  */
13631
- const LIGHT_DIRECTION = normalizeVector3({
13630
+ const LIGHT_DIRECTION$1 = normalizeVector3({
13632
13631
  x: 0.34,
13633
13632
  y: -0.62,
13634
13633
  z: 1,
@@ -13638,7 +13637,7 @@
13638
13637
  *
13639
13638
  * @private helper of `octopus3d3AvatarVisual`
13640
13639
  */
13641
- const OCTOPUS_TENTACLE_COUNT = 8;
13640
+ const OCTOPUS_TENTACLE_COUNT$1 = 8;
13642
13641
  /**
13643
13642
  * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
13644
13643
  * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
@@ -13777,9 +13776,9 @@
13777
13776
  * @private helper of `octopus3d3AvatarVisual`
13778
13777
  */
13779
13778
  function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
13780
- return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
13779
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
13781
13780
  const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
13782
- const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
13781
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
13783
13782
  return {
13784
13783
  centerLongitude: -Math.PI * 0.86 +
13785
13784
  progress * Math.PI * 1.72 +
@@ -13846,13 +13845,13 @@
13846
13845
  *
13847
13846
  * @private helper of `octopus3d3AvatarVisual`
13848
13847
  */
13849
- const LATITUDE_PATCH_COUNT = 16;
13848
+ const LATITUDE_PATCH_COUNT$1 = 16;
13850
13849
  /**
13851
13850
  * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
13852
13851
  *
13853
13852
  * @private helper of `octopus3d3AvatarVisual`
13854
13853
  */
13855
- const LONGITUDE_PATCH_COUNT = 40;
13854
+ const LONGITUDE_PATCH_COUNT$1 = 40;
13856
13855
  /**
13857
13856
  * Resolves visible projected patches for the continuous octopus mesh.
13858
13857
  *
@@ -13866,8 +13865,8 @@
13866
13865
  */
13867
13866
  function resolveVisibleContinuousOctopusPatches(options) {
13868
13867
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
13869
- const latitudePatchCount = LATITUDE_PATCH_COUNT;
13870
- const longitudePatchCount = LONGITUDE_PATCH_COUNT;
13868
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
13869
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
13871
13870
  const surfacePatches = [];
13872
13871
  const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
13873
13872
  const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
@@ -13935,7 +13934,7 @@
13935
13934
  transformedCorners[2].z +
13936
13935
  transformedCorners[3].z) /
13937
13936
  4,
13938
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
13937
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
13939
13938
  fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
13940
13939
  outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
13941
13940
  });
@@ -13969,9 +13968,9 @@
13969
13968
  const cosineLatitude = Math.max(0, Math.cos(latitude));
13970
13969
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
13971
13970
  const upperBlend = Math.pow(1 - verticalProgress, 1.28);
13972
- const lowerBlend = smoothStep(0.38, 1, verticalProgress);
13973
- const tipBlend = smoothStep(0.68, 1, verticalProgress);
13974
- const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
13971
+ const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
13972
+ const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
13973
+ const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
13975
13974
  const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
13976
13975
  const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
13977
13976
  animationPhase * 0.6 +
@@ -14037,7 +14036,7 @@
14037
14036
  let weightedDepthScale = 0;
14038
14037
  let weightedPhase = 0;
14039
14038
  for (const tentacleProfile of options.tentacleProfiles) {
14040
- const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
14039
+ const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
14041
14040
  const width = 0.2 * tentacleProfile.widthScale;
14042
14041
  const weight = Math.exp(-(distance * distance) / (width * width));
14043
14042
  totalWeight += weight;
@@ -14077,7 +14076,7 @@
14077
14076
  */
14078
14077
  function resolveContinuousLobeWave(options, longitude) {
14079
14078
  const { morphologyProfile, animationPhase, timeMs } = options;
14080
- return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
14079
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
14081
14080
  animationPhase +
14082
14081
  timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
14083
14082
  1) /
@@ -14231,7 +14230,7 @@
14231
14230
  *
14232
14231
  * @private helper of `octopus3d3AvatarVisual`
14233
14232
  */
14234
- function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
14233
+ function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
14235
14234
  return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
14236
14235
  }
14237
14236
  /**
@@ -14239,11 +14238,767 @@
14239
14238
  *
14240
14239
  * @private helper of `octopus3d3AvatarVisual`
14241
14240
  */
14242
- function smoothStep(edgeStart, edgeEnd, value) {
14241
+ function smoothStep$1(edgeStart, edgeEnd, value) {
14243
14242
  const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
14244
14243
  return progress * progress * (3 - 2 * progress);
14245
14244
  }
14246
14245
 
14246
+ /* eslint-disable no-magic-numbers */
14247
+ /**
14248
+ * Light direction used by the continuous Octopus 3D 4 mesh shading.
14249
+ *
14250
+ * @private helper of `octopus3d4AvatarVisual`
14251
+ */
14252
+ const LIGHT_DIRECTION = normalizeVector3({
14253
+ x: 0.32,
14254
+ y: -0.66,
14255
+ z: 1,
14256
+ });
14257
+ /**
14258
+ * Rim-light direction used to brighten the side of the silhouette.
14259
+ *
14260
+ * @private helper of `octopus3d4AvatarVisual`
14261
+ */
14262
+ const RIM_LIGHT_DIRECTION = normalizeVector3({
14263
+ x: -0.7,
14264
+ y: -0.18,
14265
+ z: 0.55,
14266
+ });
14267
+ /**
14268
+ * Real-octopus tentacle count used by the continuous lower mesh.
14269
+ *
14270
+ * @private helper of `octopus3d4AvatarVisual`
14271
+ */
14272
+ const OCTOPUS_TENTACLE_COUNT = 8;
14273
+ /**
14274
+ * Number of seeded skin spots painted across the octopus body.
14275
+ *
14276
+ * @private helper of `octopus3d4AvatarVisual`
14277
+ */
14278
+ const SKIN_SPOT_COUNT = 14;
14279
+ /**
14280
+ * Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
14281
+ *
14282
+ * @private helper of `octopus3d4AvatarVisual`
14283
+ */
14284
+ const octopus3d4StableStateCache = new WeakMap();
14285
+ /**
14286
+ * Returns the stable per-avatar state, computing it on first access and caching afterwards.
14287
+ *
14288
+ * @private helper of `octopus3d4AvatarVisual`
14289
+ */
14290
+ function getOctopus3d4StableState(createRandom) {
14291
+ const cached = octopus3d4StableStateCache.get(createRandom);
14292
+ if (cached !== undefined) {
14293
+ return cached;
14294
+ }
14295
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
14296
+ const animationRandom = createRandom('octopus3d4-animation-profile');
14297
+ const eyeRandom = createRandom('octopus3d4-eye-profile');
14298
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
14299
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
14300
+ const state = {
14301
+ morphologyProfile,
14302
+ animationPhase: animationRandom() * Math.PI * 2,
14303
+ leftEyePhaseOffset,
14304
+ rightEyePhaseOffset,
14305
+ tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
14306
+ skinSpots: createBlobbySkinSpots(createRandom),
14307
+ };
14308
+ octopus3d4StableStateCache.set(createRandom, state);
14309
+ return state;
14310
+ }
14311
+ /**
14312
+ * Octopus 3D 4 avatar visual.
14313
+ *
14314
+ * @private built-in avatar visual
14315
+ */
14316
+ const octopus3d4AvatarVisual = {
14317
+ id: 'octopus3d4',
14318
+ title: 'Octopus 3D 4',
14319
+ 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.',
14320
+ isAnimated: true,
14321
+ supportsPointerTracking: true,
14322
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
14323
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
14324
+ const sceneCenterX = size * 0.5;
14325
+ const sceneCenterY = size * 0.535;
14326
+ const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
14327
+ const meshCenter = {
14328
+ x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
14329
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
14330
+ z: interaction.intensity * size * 0.02,
14331
+ };
14332
+ const rotationY = -0.08 +
14333
+ Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
14334
+ interaction.bodyOffsetX * 0.24 +
14335
+ interaction.gazeX * 0.98;
14336
+ const rotationX = -0.07 +
14337
+ Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
14338
+ interaction.bodyOffsetY * 0.08 -
14339
+ interaction.gazeY * 0.42;
14340
+ const surfaceOptions = {
14341
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
14342
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
14343
+ radiusZ: size *
14344
+ morphologyProfile.body.bodyRadiusRatio *
14345
+ (1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
14346
+ morphologyProfile,
14347
+ timeMs,
14348
+ animationPhase,
14349
+ tentacleProfiles,
14350
+ };
14351
+ const surfacePatches = resolveVisibleBlobbyContinuousPatches({
14352
+ ...surfaceOptions,
14353
+ center: meshCenter,
14354
+ rotationX,
14355
+ rotationY,
14356
+ sceneCenterX,
14357
+ sceneCenterY,
14358
+ size,
14359
+ palette,
14360
+ });
14361
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
14362
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
14363
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
14364
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
14365
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
14366
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
14367
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
14368
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
14369
+ drawAvatarFrame(context, size, palette);
14370
+ drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
14371
+ drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
14372
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
14373
+ drawBlobbyContinuousSurfacePatch(context, surfacePatch);
14374
+ }
14375
+ drawBlobbySkinSpots({
14376
+ context,
14377
+ surfaceOptions,
14378
+ center: meshCenter,
14379
+ rotationX,
14380
+ rotationY,
14381
+ sceneCenterX,
14382
+ sceneCenterY,
14383
+ size,
14384
+ palette,
14385
+ skinSpots,
14386
+ });
14387
+ drawBlobbyContinuousCurrents({
14388
+ context,
14389
+ surfaceOptions,
14390
+ center: meshCenter,
14391
+ rotationX,
14392
+ rotationY,
14393
+ sceneCenterX,
14394
+ sceneCenterY,
14395
+ size,
14396
+ palette,
14397
+ morphologyProfile,
14398
+ timeMs,
14399
+ animationPhase,
14400
+ });
14401
+ drawBlobbyContinuousSuckers({
14402
+ context,
14403
+ surfaceOptions,
14404
+ center: meshCenter,
14405
+ rotationX,
14406
+ rotationY,
14407
+ sceneCenterX,
14408
+ sceneCenterY,
14409
+ size,
14410
+ palette,
14411
+ });
14412
+ drawBlobbyContinuousGloss({
14413
+ context,
14414
+ surfaceOptions,
14415
+ center: meshCenter,
14416
+ rotationX,
14417
+ rotationY,
14418
+ sceneCenterX,
14419
+ sceneCenterY,
14420
+ size,
14421
+ palette,
14422
+ });
14423
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
14424
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
14425
+ drawProjectedOrganicMouth(context, [
14426
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
14427
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
14428
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
14429
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
14430
+ },
14431
+ };
14432
+ /**
14433
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
14434
+ *
14435
+ * @private helper of `octopus3d4AvatarVisual`
14436
+ */
14437
+ function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
14438
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
14439
+ const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
14440
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
14441
+ return {
14442
+ centerLongitude: -Math.PI * 0.9 +
14443
+ progress * Math.PI * 1.8 +
14444
+ (tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
14445
+ widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
14446
+ lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
14447
+ swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
14448
+ depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
14449
+ curlScale: 0.62 + tentacleRandom() * 0.46,
14450
+ primaryPhase: tentacleRandom() * Math.PI * 2,
14451
+ secondaryPhase: tentacleRandom() * Math.PI * 2,
14452
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
14453
+ };
14454
+ });
14455
+ }
14456
+ /**
14457
+ * Creates seeded skin pigment spots distributed across the upper octopus mesh.
14458
+ *
14459
+ * @private helper of `octopus3d4AvatarVisual`
14460
+ */
14461
+ function createBlobbySkinSpots(createRandom) {
14462
+ const spotRandom = createRandom('octopus3d4-skin-spots');
14463
+ return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
14464
+ latitude: -0.45 + spotRandom() * 0.7,
14465
+ longitude: -0.5 + spotRandom() * 1.0,
14466
+ radiusScale: 0.0028 + spotRandom() * 0.0052,
14467
+ opacity: 0.16 + spotRandom() * 0.22,
14468
+ }));
14469
+ }
14470
+ /**
14471
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
14472
+ *
14473
+ * @private helper of `octopus3d4AvatarVisual`
14474
+ */
14475
+ function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
14476
+ 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));
14477
+ glowGradient.addColorStop(0, `${palette.highlight}74`);
14478
+ glowGradient.addColorStop(0.32, `${palette.accent}32`);
14479
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
14480
+ context.fillStyle = glowGradient;
14481
+ context.fillRect(0, 0, size, size);
14482
+ 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);
14483
+ lowerGradient.addColorStop(0, `${palette.secondary}28`);
14484
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
14485
+ context.fillStyle = lowerGradient;
14486
+ context.fillRect(0, 0, size, size);
14487
+ }
14488
+ /**
14489
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
14490
+ *
14491
+ * @private helper of `octopus3d4AvatarVisual`
14492
+ */
14493
+ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
14494
+ const cx = size * 0.5 + interaction.gazeX * size * 0.046;
14495
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
14496
+ const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
14497
+ const ry = size * 0.062;
14498
+ context.save();
14499
+ context.translate(cx, cy);
14500
+ context.scale(1, ry / rx);
14501
+ const blurRadius = rx * 1.42;
14502
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
14503
+ shadowGradient.addColorStop(0, `${palette.shadow}82`);
14504
+ shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
14505
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
14506
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
14507
+ context.fillStyle = shadowGradient;
14508
+ context.beginPath();
14509
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
14510
+ context.fill();
14511
+ context.restore();
14512
+ }
14513
+ /**
14514
+ * Number of latitude segments used by the continuous Octopus 3D 4 mesh.
14515
+ *
14516
+ * @private helper of `octopus3d4AvatarVisual`
14517
+ */
14518
+ const LATITUDE_PATCH_COUNT = 20;
14519
+ /**
14520
+ * Number of longitude segments used by the continuous Octopus 3D 4 mesh.
14521
+ *
14522
+ * @private helper of `octopus3d4AvatarVisual`
14523
+ */
14524
+ const LONGITUDE_PATCH_COUNT = 48;
14525
+ /**
14526
+ * Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
14527
+ *
14528
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
14529
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
14530
+ * than re-evaluated for every patch corner.
14531
+ *
14532
+ * @private helper of `octopus3d4AvatarVisual`
14533
+ */
14534
+ function resolveVisibleBlobbyContinuousPatches(options) {
14535
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
14536
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
14537
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
14538
+ const surfacePatches = [];
14539
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
14540
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
14541
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
14542
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
14543
+ }
14544
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
14545
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
14546
+ }
14547
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
14548
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
14549
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
14550
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
14551
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
14552
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
14553
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
14554
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
14555
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
14556
+ }
14557
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
14558
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
14559
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
14560
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
14561
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
14562
+ }
14563
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
14564
+ const transformedCornerSamples = new Array(cornerCount);
14565
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
14566
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
14567
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
14568
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
14569
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
14570
+ const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
14571
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
14572
+ }
14573
+ }
14574
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
14575
+ const startLatitude = latitudeBoundaries[latitudeIndex];
14576
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
14577
+ const centerLatitude = (startLatitude + endLatitude) / 2;
14578
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
14579
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
14580
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
14581
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
14582
+ const transformedCorners = [
14583
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
14584
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
14585
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
14586
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
14587
+ ];
14588
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
14589
+ if (surfaceNormal.z <= 0.006) {
14590
+ continue;
14591
+ }
14592
+ const projectedCorners = [
14593
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
14594
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
14595
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
14596
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
14597
+ ];
14598
+ const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
14599
+ const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
14600
+ surfacePatches.push({
14601
+ corners: projectedCorners,
14602
+ averageDepth: (transformedCorners[0].z +
14603
+ transformedCorners[1].z +
14604
+ transformedCorners[2].z +
14605
+ transformedCorners[3].z) /
14606
+ 4,
14607
+ lightIntensity,
14608
+ rimLightIntensity,
14609
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
14610
+ outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
14611
+ });
14612
+ }
14613
+ }
14614
+ return surfacePatches;
14615
+ }
14616
+ /**
14617
+ * Samples one point on the continuous Octopus 3D 4 surface.
14618
+ *
14619
+ * @private helper of `octopus3d4AvatarVisual`
14620
+ */
14621
+ function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
14622
+ return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
14623
+ }
14624
+ /**
14625
+ * Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
14626
+ *
14627
+ * The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
14628
+ * across every latitude row and each tentacle/lobe value is computed once per frame instead
14629
+ * of `latitudePatchCount * 4` times.
14630
+ *
14631
+ * @private helper of `octopus3d4AvatarVisual`
14632
+ */
14633
+ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
14634
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
14635
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
14636
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
14637
+ const upperBlend = Math.pow(1 - verticalProgress, 1.32);
14638
+ const lowerBlend = smoothStep(0.34, 1, verticalProgress);
14639
+ const tipBlend = smoothStep(0.66, 1, verticalProgress);
14640
+ const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
14641
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
14642
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
14643
+ animationPhase * 0.6 +
14644
+ timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
14645
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
14646
+ (0.32 + lowerBlend * 0.7);
14647
+ const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
14648
+ lowerBlend *
14649
+ tentacleInfluence.core *
14650
+ tentacleInfluence.swayScale;
14651
+ const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
14652
+ lowerBlend *
14653
+ tentacleInfluence.core *
14654
+ tentacleInfluence.swayScale *
14655
+ 0.42;
14656
+ const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
14657
+ tipBlend *
14658
+ tentacleInfluence.core *
14659
+ tentacleInfluence.curlScale *
14660
+ 0.36;
14661
+ const horizontalScale = 1.06 +
14662
+ mantleRipple +
14663
+ lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
14664
+ lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
14665
+ upperBlend * 0.08;
14666
+ const depthScale = 1.08 +
14667
+ upperBlend * 0.16 +
14668
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
14669
+ lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
14670
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
14671
+ const tentacleTubeRadius = lowerBlend *
14672
+ tentacleInfluence.core *
14673
+ (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
14674
+ radiusX;
14675
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
14676
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
14677
+ const lowerDrop = lowerBlend *
14678
+ radiusY *
14679
+ (0.2 +
14680
+ tentacleInfluence.core *
14681
+ (0.42 +
14682
+ tentacleInfluence.lengthScale * 0.24 +
14683
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
14684
+ const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
14685
+ return {
14686
+ x: Math.sin(effectiveLongitude) * planarRadiusX +
14687
+ combinedTentacleSway +
14688
+ tentacleCurl * radiusX * 0.18,
14689
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
14690
+ upperBlend * radiusY * 0.12 +
14691
+ lowerDrop +
14692
+ Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
14693
+ Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
14694
+ lowerBlend *
14695
+ tipBlend *
14696
+ tentacleInfluence.core *
14697
+ radiusY *
14698
+ 0.04,
14699
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
14700
+ Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
14701
+ lowerBlend *
14702
+ tentacleInfluence.core *
14703
+ radiusZ *
14704
+ 0.044 +
14705
+ tentacleCurl * radiusZ * 0.14,
14706
+ };
14707
+ }
14708
+ /**
14709
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
14710
+ *
14711
+ * @private helper of `octopus3d4AvatarVisual`
14712
+ */
14713
+ function resolveBlobbyTentacleInfluence(options, longitude) {
14714
+ let totalWeight = 0;
14715
+ let weightedSin = 0;
14716
+ let weightedCos = 0;
14717
+ let weightedWidthScale = 0;
14718
+ let weightedLengthScale = 0;
14719
+ let weightedSwayScale = 0;
14720
+ let weightedDepthScale = 0;
14721
+ let weightedCurlScale = 0;
14722
+ let weightedPrimaryPhase = 0;
14723
+ let weightedSecondaryPhase = 0;
14724
+ for (const tentacleProfile of options.tentacleProfiles) {
14725
+ const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
14726
+ const width = 0.22 * tentacleProfile.widthScale;
14727
+ const weight = Math.exp(-(distance * distance) / (width * width));
14728
+ totalWeight += weight;
14729
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
14730
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
14731
+ weightedWidthScale += tentacleProfile.widthScale * weight;
14732
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
14733
+ weightedSwayScale += tentacleProfile.swayScale * weight;
14734
+ weightedDepthScale += tentacleProfile.depthScale * weight;
14735
+ weightedCurlScale += tentacleProfile.curlScale * weight;
14736
+ weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
14737
+ weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
14738
+ }
14739
+ if (totalWeight < 0.0001) {
14740
+ return {
14741
+ core: 0,
14742
+ centerLongitude: longitude,
14743
+ widthScale: 1,
14744
+ lengthScale: 1,
14745
+ swayScale: 1,
14746
+ depthScale: 1,
14747
+ curlScale: 1,
14748
+ primaryPhase: 0,
14749
+ secondaryPhase: 0,
14750
+ };
14751
+ }
14752
+ return {
14753
+ core: clampNumber$1(totalWeight, 0, 1),
14754
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
14755
+ widthScale: weightedWidthScale / totalWeight,
14756
+ lengthScale: weightedLengthScale / totalWeight,
14757
+ swayScale: weightedSwayScale / totalWeight,
14758
+ depthScale: weightedDepthScale / totalWeight,
14759
+ curlScale: weightedCurlScale / totalWeight,
14760
+ primaryPhase: weightedPrimaryPhase / totalWeight,
14761
+ secondaryPhase: weightedSecondaryPhase / totalWeight,
14762
+ };
14763
+ }
14764
+ /**
14765
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
14766
+ *
14767
+ * @private helper of `octopus3d4AvatarVisual`
14768
+ */
14769
+ function resolveBlobbyLobeWave(options, longitude) {
14770
+ const { morphologyProfile, animationPhase, timeMs } = options;
14771
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
14772
+ animationPhase +
14773
+ timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
14774
+ 1) /
14775
+ 2);
14776
+ }
14777
+ /**
14778
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
14779
+ *
14780
+ * @private helper of `octopus3d4AvatarVisual`
14781
+ */
14782
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
14783
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
14784
+ if (tonalProgress < 0.12) {
14785
+ return palette.highlight;
14786
+ }
14787
+ if (tonalProgress < 0.3) {
14788
+ return palette.secondary;
14789
+ }
14790
+ if (tonalProgress < 0.72) {
14791
+ return forwardness > 0.56 ? palette.secondary : palette.primary;
14792
+ }
14793
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
14794
+ }
14795
+ /**
14796
+ * Draws one projected mesh patch with multi-pass shading and a soft edge.
14797
+ *
14798
+ * @private helper of `octopus3d4AvatarVisual`
14799
+ */
14800
+ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
14801
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
14802
+ if (surfacePatch.lightIntensity > 0) {
14803
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
14804
+ }
14805
+ else if (surfacePatch.lightIntensity < 0) {
14806
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
14807
+ }
14808
+ if (surfacePatch.rimLightIntensity > 0.04) {
14809
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
14810
+ }
14811
+ context.save();
14812
+ context.beginPath();
14813
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
14814
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
14815
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
14816
+ }
14817
+ context.closePath();
14818
+ context.strokeStyle = surfacePatch.outlineColor;
14819
+ context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
14820
+ context.lineJoin = 'round';
14821
+ context.stroke();
14822
+ context.restore();
14823
+ }
14824
+ /**
14825
+ * Draws projected mantle-current lines on the front of the mesh.
14826
+ *
14827
+ * @private helper of `octopus3d4AvatarVisual`
14828
+ */
14829
+ function drawBlobbyContinuousCurrents(options) {
14830
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
14831
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
14832
+ const centerIndex = (currentCount - 1) / 2;
14833
+ context.save();
14834
+ context.lineCap = 'round';
14835
+ context.lineJoin = 'round';
14836
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
14837
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
14838
+ const projectedPoints = [];
14839
+ for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
14840
+ const progress = sampleIndex / 8;
14841
+ const latitude = -0.48 + progress * 0.78;
14842
+ const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
14843
+ const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
14844
+ if (scenePoint.z > center.z - size * 0.016) {
14845
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
14846
+ }
14847
+ }
14848
+ if (projectedPoints.length < 3) {
14849
+ continue;
14850
+ }
14851
+ context.beginPath();
14852
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
14853
+ for (const projectedPoint of projectedPoints.slice(1)) {
14854
+ context.lineTo(projectedPoint.x, projectedPoint.y);
14855
+ }
14856
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
14857
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
14858
+ context.stroke();
14859
+ }
14860
+ context.restore();
14861
+ }
14862
+ /**
14863
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
14864
+ *
14865
+ * @private helper of `octopus3d4AvatarVisual`
14866
+ */
14867
+ function drawBlobbyContinuousSuckers(options) {
14868
+ const { surfaceOptions, size } = options;
14869
+ const { timeMs } = surfaceOptions;
14870
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
14871
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
14872
+ continue;
14873
+ }
14874
+ for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
14875
+ const latitude = 0.5 + suckerIndex * 0.12;
14876
+ const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
14877
+ const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
14878
+ drawBlobbyContinuousSurfaceSpot({
14879
+ ...options,
14880
+ latitude,
14881
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
14882
+ radiusScale: size * (0.0068 - suckerIndex * 0.0006),
14883
+ });
14884
+ }
14885
+ }
14886
+ }
14887
+ /**
14888
+ * Draws seeded pigment spots across the upper mesh for a richer skin texture.
14889
+ *
14890
+ * @private helper of `octopus3d4AvatarVisual`
14891
+ */
14892
+ function drawBlobbySkinSpots(options) {
14893
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
14894
+ for (const skinSpot of skinSpots) {
14895
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
14896
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
14897
+ if (sceneCenterPoint.z <= center.z - size * 0.01) {
14898
+ continue;
14899
+ }
14900
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
14901
+ const spotRadius = size * skinSpot.radiusScale;
14902
+ context.save();
14903
+ context.beginPath();
14904
+ context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
14905
+ context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
14906
+ context.fill();
14907
+ context.restore();
14908
+ }
14909
+ }
14910
+ /**
14911
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
14912
+ *
14913
+ * @private helper of `octopus3d4AvatarVisual`
14914
+ */
14915
+ function drawBlobbyContinuousSurfaceSpot(options) {
14916
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
14917
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
14918
+ const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
14919
+ const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
14920
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
14921
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
14922
+ return;
14923
+ }
14924
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
14925
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
14926
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
14927
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
14928
+ radiusScale *
14929
+ 0.78, size * 0.003, size * 0.02);
14930
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
14931
+ radiusScale *
14932
+ 0.54, size * 0.0024, size * 0.015);
14933
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
14934
+ context.save();
14935
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
14936
+ context.rotate(rotation);
14937
+ context.beginPath();
14938
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
14939
+ context.fillStyle = `${palette.highlight}80`;
14940
+ context.fill();
14941
+ context.strokeStyle = `${palette.highlight}a8`;
14942
+ context.lineWidth = Math.max(0.7, size * 0.0028);
14943
+ context.stroke();
14944
+ context.restore();
14945
+ }
14946
+ /**
14947
+ * Draws a soft, slowly drifting gloss highlight on the front of the mesh.
14948
+ *
14949
+ * @private helper of `octopus3d4AvatarVisual`
14950
+ */
14951
+ function drawBlobbyContinuousGloss(options) {
14952
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
14953
+ const { timeMs, animationPhase } = surfaceOptions;
14954
+ const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
14955
+ const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
14956
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
14957
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
14958
+ if (sceneCenterPoint.z <= center.z) {
14959
+ return;
14960
+ }
14961
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
14962
+ const glossRadius = size * 0.058;
14963
+ context.save();
14964
+ const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
14965
+ glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
14966
+ glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
14967
+ glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
14968
+ context.fillStyle = glossGradient;
14969
+ context.beginPath();
14970
+ context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
14971
+ context.fill();
14972
+ context.restore();
14973
+ }
14974
+ /**
14975
+ * Resolves a signed angular distance from the source longitude to the target longitude.
14976
+ *
14977
+ * @private helper of `octopus3d4AvatarVisual`
14978
+ */
14979
+ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
14980
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
14981
+ }
14982
+ /**
14983
+ * Smoothly maps a value between two bounds into `[0, 1]`.
14984
+ *
14985
+ * @private helper of `octopus3d4AvatarVisual`
14986
+ */
14987
+ function smoothStep(edgeStart, edgeEnd, value) {
14988
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
14989
+ return progress * progress * (3 - 2 * progress);
14990
+ }
14991
+ /**
14992
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
14993
+ *
14994
+ * @private helper of `octopus3d4AvatarVisual`
14995
+ */
14996
+ function formatAlphaHex(opacity) {
14997
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
14998
+ .toString(16)
14999
+ .padStart(2, '0');
15000
+ }
15001
+
14247
15002
  /* eslint-disable no-magic-numbers */
14248
15003
  /**
14249
15004
  * Octopus avatar visual.
@@ -15015,6 +15770,7 @@
15015
15770
  octopus3dAvatarVisual,
15016
15771
  octopus3d2AvatarVisual,
15017
15772
  octopus3d3AvatarVisual,
15773
+ octopus3d4AvatarVisual,
15018
15774
  asciiOctopusAvatarVisual,
15019
15775
  minecraftAvatarVisual,
15020
15776
  minecraft2AvatarVisual,