@promptbook/node 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.
package/esm/index.es.js CHANGED
@@ -35,7 +35,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
35
35
  * @generated
36
36
  * @see https://github.com/webgptorg/promptbook
37
37
  */
38
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-134';
38
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-136';
39
39
  /**
40
40
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
41
41
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -12313,6 +12313,11 @@ function isVoidPseudoAgentReference(rawReference) {
12313
12313
  * - `isValidAgentUrl` *(this one)* which tests just agent URL
12314
12314
  * - `isValidPipelineUrl` which tests just pipeline URL
12315
12315
  *
12316
+ * Note: This is a pure structural validator and does not block private/internal network
12317
+ * addresses. Callers that fetch the URL server-side from an untrusted network context must
12318
+ * additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
12319
+ * outbound request.
12320
+ *
12316
12321
  * @public exported from `@promptbook/utils`
12317
12322
  */
12318
12323
  function isValidAgentUrl(url) {
@@ -12326,12 +12331,6 @@ function isValidAgentUrl(url) {
12326
12331
  // TODO: [🐠]
12327
12332
  return false;
12328
12333
  }
12329
- /*
12330
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
12331
- if (isUrlOnPrivateNetwork(url)) {
12332
- return false;
12333
- }
12334
- */
12335
12334
  return true;
12336
12335
  }
12337
12336
  // TODO: [🐠] Maybe more info why the URL is invalid
@@ -15961,7 +15960,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
15961
15960
  *
15962
15961
  * @private helper of `minecraft2AvatarVisual`
15963
15962
  */
15964
- const LIGHT_DIRECTION$3 = normalizeVector3({
15963
+ const LIGHT_DIRECTION$4 = normalizeVector3({
15965
15964
  x: 0.4,
15966
15965
  y: -0.65,
15967
15966
  z: 0.92,
@@ -16185,7 +16184,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
16185
16184
  corners: projectedCorners,
16186
16185
  texture: faceDefinition.texture,
16187
16186
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
16188
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$3), -1, 1),
16187
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
16189
16188
  outlineColor: cuboid.outlineColor,
16190
16189
  };
16191
16190
  });
@@ -16872,7 +16871,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
16872
16871
  *
16873
16872
  * @private helper of `octopus3AvatarVisual`
16874
16873
  */
16875
- function formatAlphaHex$1(opacity) {
16874
+ function formatAlphaHex$2(opacity) {
16876
16875
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
16877
16876
  .toString(16)
16878
16877
  .padStart(2, '0');
@@ -17251,7 +17250,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
17251
17250
  context.beginPath();
17252
17251
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
17253
17252
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
17254
- context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
17253
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
17255
17254
  context.lineWidth = radiusX * 0.08;
17256
17255
  context.lineCap = 'round';
17257
17256
  context.stroke();
@@ -17332,7 +17331,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
17332
17331
  context.beginPath();
17333
17332
  context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
17334
17333
  context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
17335
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
17334
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
17336
17335
  context.lineWidth = projectedRadiusX * 0.08;
17337
17336
  context.lineCap = 'round';
17338
17337
  context.stroke();
@@ -17378,7 +17377,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
17378
17377
  *
17379
17378
  * @private helper of the 3D octopus avatar visuals
17380
17379
  */
17381
- function formatAlphaHex(opacity) {
17380
+ function formatAlphaHex$1(opacity) {
17382
17381
  return Math.round(clampNumber$1(opacity, 0, 1) * 255)
17383
17382
  .toString(16)
17384
17383
  .padStart(2, '0');
@@ -17390,7 +17389,7 @@ function formatAlphaHex(opacity) {
17390
17389
  *
17391
17390
  * @private helper of `octopus3dAvatarVisual`
17392
17391
  */
17393
- const LIGHT_DIRECTION$2 = normalizeVector3({
17392
+ const LIGHT_DIRECTION$3 = normalizeVector3({
17394
17393
  x: 0.48,
17395
17394
  y: -0.62,
17396
17395
  z: 0.94,
@@ -17640,7 +17639,7 @@ function resolveVisibleEllipsoidPatches(options) {
17640
17639
  corners: projectedCorners,
17641
17640
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
17642
17641
  transformedCorners.length,
17643
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
17642
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
17644
17643
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
17645
17644
  outlineColor,
17646
17645
  });
@@ -17829,7 +17828,7 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
17829
17828
  *
17830
17829
  * @private helper of `octopus3d2AvatarVisual`
17831
17830
  */
17832
- const LIGHT_DIRECTION$1 = normalizeVector3({
17831
+ const LIGHT_DIRECTION$2 = normalizeVector3({
17833
17832
  x: 0.38,
17834
17833
  y: -0.6,
17835
17834
  z: 0.98,
@@ -17988,13 +17987,13 @@ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, mo
17988
17987
  *
17989
17988
  * @private helper of `octopus3d2AvatarVisual`
17990
17989
  */
17991
- const LATITUDE_PATCH_COUNT$1 = 12;
17990
+ const LATITUDE_PATCH_COUNT$2 = 12;
17992
17991
  /**
17993
17992
  * Number of longitude segments used by the single blobby octopus mesh.
17994
17993
  *
17995
17994
  * @private helper of `octopus3d2AvatarVisual`
17996
17995
  */
17997
- const LONGITUDE_PATCH_COUNT$1 = 24;
17996
+ const LONGITUDE_PATCH_COUNT$2 = 24;
17998
17997
  /**
17999
17998
  * Resolves all visible projected patches for the single blobby octopus mesh.
18000
17999
  *
@@ -18008,8 +18007,8 @@ const LONGITUDE_PATCH_COUNT$1 = 24;
18008
18007
  */
18009
18008
  function resolveVisibleBlobbyOctopusPatches(options) {
18010
18009
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
18011
- const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
18012
- const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
18010
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
18011
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
18013
18012
  const surfacePatches = [];
18014
18013
  const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
18015
18014
  const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
@@ -18068,8 +18067,8 @@ function resolveVisibleBlobbyOctopusPatches(options) {
18068
18067
  transformedCorners[2].z +
18069
18068
  transformedCorners[3].z) /
18070
18069
  4,
18071
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
18072
- fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
18070
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
18071
+ fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
18073
18072
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
18074
18073
  });
18075
18074
  }
@@ -18137,7 +18136,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
18137
18136
  *
18138
18137
  * @private helper of `octopus3d2AvatarVisual`
18139
18138
  */
18140
- function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
18139
+ function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
18141
18140
  const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
18142
18141
  if (tonalProgress < 0.16) {
18143
18142
  return palette.highlight;
@@ -18183,7 +18182,7 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
18183
18182
  *
18184
18183
  * @private helper of `octopus3d3AvatarVisual`
18185
18184
  */
18186
- const LIGHT_DIRECTION = normalizeVector3({
18185
+ const LIGHT_DIRECTION$1 = normalizeVector3({
18187
18186
  x: 0.34,
18188
18187
  y: -0.62,
18189
18188
  z: 1,
@@ -18193,7 +18192,7 @@ const LIGHT_DIRECTION = normalizeVector3({
18193
18192
  *
18194
18193
  * @private helper of `octopus3d3AvatarVisual`
18195
18194
  */
18196
- const OCTOPUS_TENTACLE_COUNT = 8;
18195
+ const OCTOPUS_TENTACLE_COUNT$1 = 8;
18197
18196
  /**
18198
18197
  * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
18199
18198
  * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
@@ -18332,9 +18331,9 @@ const octopus3d3AvatarVisual = {
18332
18331
  * @private helper of `octopus3d3AvatarVisual`
18333
18332
  */
18334
18333
  function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
18335
- return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
18334
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
18336
18335
  const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
18337
- const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
18336
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
18338
18337
  return {
18339
18338
  centerLongitude: -Math.PI * 0.86 +
18340
18339
  progress * Math.PI * 1.72 +
@@ -18401,13 +18400,13 @@ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs
18401
18400
  *
18402
18401
  * @private helper of `octopus3d3AvatarVisual`
18403
18402
  */
18404
- const LATITUDE_PATCH_COUNT = 16;
18403
+ const LATITUDE_PATCH_COUNT$1 = 16;
18405
18404
  /**
18406
18405
  * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
18407
18406
  *
18408
18407
  * @private helper of `octopus3d3AvatarVisual`
18409
18408
  */
18410
- const LONGITUDE_PATCH_COUNT = 40;
18409
+ const LONGITUDE_PATCH_COUNT$1 = 40;
18411
18410
  /**
18412
18411
  * Resolves visible projected patches for the continuous octopus mesh.
18413
18412
  *
@@ -18421,8 +18420,8 @@ const LONGITUDE_PATCH_COUNT = 40;
18421
18420
  */
18422
18421
  function resolveVisibleContinuousOctopusPatches(options) {
18423
18422
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
18424
- const latitudePatchCount = LATITUDE_PATCH_COUNT;
18425
- const longitudePatchCount = LONGITUDE_PATCH_COUNT;
18423
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
18424
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
18426
18425
  const surfacePatches = [];
18427
18426
  const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
18428
18427
  const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
@@ -18490,7 +18489,7 @@ function resolveVisibleContinuousOctopusPatches(options) {
18490
18489
  transformedCorners[2].z +
18491
18490
  transformedCorners[3].z) /
18492
18491
  4,
18493
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
18492
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
18494
18493
  fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
18495
18494
  outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
18496
18495
  });
@@ -18524,9 +18523,9 @@ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude
18524
18523
  const cosineLatitude = Math.max(0, Math.cos(latitude));
18525
18524
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
18526
18525
  const upperBlend = Math.pow(1 - verticalProgress, 1.28);
18527
- const lowerBlend = smoothStep(0.38, 1, verticalProgress);
18528
- const tipBlend = smoothStep(0.68, 1, verticalProgress);
18529
- const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
18526
+ const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
18527
+ const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
18528
+ const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
18530
18529
  const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
18531
18530
  const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
18532
18531
  animationPhase * 0.6 +
@@ -18592,7 +18591,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
18592
18591
  let weightedDepthScale = 0;
18593
18592
  let weightedPhase = 0;
18594
18593
  for (const tentacleProfile of options.tentacleProfiles) {
18595
- const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
18594
+ const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
18596
18595
  const width = 0.2 * tentacleProfile.widthScale;
18597
18596
  const weight = Math.exp(-(distance * distance) / (width * width));
18598
18597
  totalWeight += weight;
@@ -18632,7 +18631,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
18632
18631
  */
18633
18632
  function resolveContinuousLobeWave(options, longitude) {
18634
18633
  const { morphologyProfile, animationPhase, timeMs } = options;
18635
- return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
18634
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
18636
18635
  animationPhase +
18637
18636
  timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
18638
18637
  1) /
@@ -18786,7 +18785,7 @@ function drawProjectedSurfaceSpot(options) {
18786
18785
  *
18787
18786
  * @private helper of `octopus3d3AvatarVisual`
18788
18787
  */
18789
- function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
18788
+ function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
18790
18789
  return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
18791
18790
  }
18792
18791
  /**
@@ -18794,11 +18793,767 @@ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
18794
18793
  *
18795
18794
  * @private helper of `octopus3d3AvatarVisual`
18796
18795
  */
18797
- function smoothStep(edgeStart, edgeEnd, value) {
18796
+ function smoothStep$1(edgeStart, edgeEnd, value) {
18798
18797
  const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
18799
18798
  return progress * progress * (3 - 2 * progress);
18800
18799
  }
18801
18800
 
18801
+ /* eslint-disable no-magic-numbers */
18802
+ /**
18803
+ * Light direction used by the continuous Octopus 3D 4 mesh shading.
18804
+ *
18805
+ * @private helper of `octopus3d4AvatarVisual`
18806
+ */
18807
+ const LIGHT_DIRECTION = normalizeVector3({
18808
+ x: 0.32,
18809
+ y: -0.66,
18810
+ z: 1,
18811
+ });
18812
+ /**
18813
+ * Rim-light direction used to brighten the side of the silhouette.
18814
+ *
18815
+ * @private helper of `octopus3d4AvatarVisual`
18816
+ */
18817
+ const RIM_LIGHT_DIRECTION = normalizeVector3({
18818
+ x: -0.7,
18819
+ y: -0.18,
18820
+ z: 0.55,
18821
+ });
18822
+ /**
18823
+ * Real-octopus tentacle count used by the continuous lower mesh.
18824
+ *
18825
+ * @private helper of `octopus3d4AvatarVisual`
18826
+ */
18827
+ const OCTOPUS_TENTACLE_COUNT = 8;
18828
+ /**
18829
+ * Number of seeded skin spots painted across the octopus body.
18830
+ *
18831
+ * @private helper of `octopus3d4AvatarVisual`
18832
+ */
18833
+ const SKIN_SPOT_COUNT = 14;
18834
+ /**
18835
+ * Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
18836
+ *
18837
+ * @private helper of `octopus3d4AvatarVisual`
18838
+ */
18839
+ const octopus3d4StableStateCache = new WeakMap();
18840
+ /**
18841
+ * Returns the stable per-avatar state, computing it on first access and caching afterwards.
18842
+ *
18843
+ * @private helper of `octopus3d4AvatarVisual`
18844
+ */
18845
+ function getOctopus3d4StableState(createRandom) {
18846
+ const cached = octopus3d4StableStateCache.get(createRandom);
18847
+ if (cached !== undefined) {
18848
+ return cached;
18849
+ }
18850
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
18851
+ const animationRandom = createRandom('octopus3d4-animation-profile');
18852
+ const eyeRandom = createRandom('octopus3d4-eye-profile');
18853
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
18854
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
18855
+ const state = {
18856
+ morphologyProfile,
18857
+ animationPhase: animationRandom() * Math.PI * 2,
18858
+ leftEyePhaseOffset,
18859
+ rightEyePhaseOffset,
18860
+ tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
18861
+ skinSpots: createBlobbySkinSpots(createRandom),
18862
+ };
18863
+ octopus3d4StableStateCache.set(createRandom, state);
18864
+ return state;
18865
+ }
18866
+ /**
18867
+ * Octopus 3D 4 avatar visual.
18868
+ *
18869
+ * @private built-in avatar visual
18870
+ */
18871
+ const octopus3d4AvatarVisual = {
18872
+ id: 'octopus3d4',
18873
+ title: 'Octopus 3D 4',
18874
+ 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.',
18875
+ isAnimated: true,
18876
+ supportsPointerTracking: true,
18877
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
18878
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
18879
+ const sceneCenterX = size * 0.5;
18880
+ const sceneCenterY = size * 0.535;
18881
+ const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
18882
+ const meshCenter = {
18883
+ x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
18884
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
18885
+ z: interaction.intensity * size * 0.02,
18886
+ };
18887
+ const rotationY = -0.08 +
18888
+ Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
18889
+ interaction.bodyOffsetX * 0.24 +
18890
+ interaction.gazeX * 0.98;
18891
+ const rotationX = -0.07 +
18892
+ Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
18893
+ interaction.bodyOffsetY * 0.08 -
18894
+ interaction.gazeY * 0.42;
18895
+ const surfaceOptions = {
18896
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
18897
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
18898
+ radiusZ: size *
18899
+ morphologyProfile.body.bodyRadiusRatio *
18900
+ (1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
18901
+ morphologyProfile,
18902
+ timeMs,
18903
+ animationPhase,
18904
+ tentacleProfiles,
18905
+ };
18906
+ const surfacePatches = resolveVisibleBlobbyContinuousPatches({
18907
+ ...surfaceOptions,
18908
+ center: meshCenter,
18909
+ rotationX,
18910
+ rotationY,
18911
+ sceneCenterX,
18912
+ sceneCenterY,
18913
+ size,
18914
+ palette,
18915
+ });
18916
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
18917
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
18918
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
18919
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
18920
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
18921
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
18922
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
18923
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
18924
+ drawAvatarFrame(context, size, palette);
18925
+ drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
18926
+ drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
18927
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
18928
+ drawBlobbyContinuousSurfacePatch(context, surfacePatch);
18929
+ }
18930
+ drawBlobbySkinSpots({
18931
+ context,
18932
+ surfaceOptions,
18933
+ center: meshCenter,
18934
+ rotationX,
18935
+ rotationY,
18936
+ sceneCenterX,
18937
+ sceneCenterY,
18938
+ size,
18939
+ palette,
18940
+ skinSpots,
18941
+ });
18942
+ drawBlobbyContinuousCurrents({
18943
+ context,
18944
+ surfaceOptions,
18945
+ center: meshCenter,
18946
+ rotationX,
18947
+ rotationY,
18948
+ sceneCenterX,
18949
+ sceneCenterY,
18950
+ size,
18951
+ palette,
18952
+ morphologyProfile,
18953
+ timeMs,
18954
+ animationPhase,
18955
+ });
18956
+ drawBlobbyContinuousSuckers({
18957
+ context,
18958
+ surfaceOptions,
18959
+ center: meshCenter,
18960
+ rotationX,
18961
+ rotationY,
18962
+ sceneCenterX,
18963
+ sceneCenterY,
18964
+ size,
18965
+ palette,
18966
+ });
18967
+ drawBlobbyContinuousGloss({
18968
+ context,
18969
+ surfaceOptions,
18970
+ center: meshCenter,
18971
+ rotationX,
18972
+ rotationY,
18973
+ sceneCenterX,
18974
+ sceneCenterY,
18975
+ size,
18976
+ palette,
18977
+ });
18978
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
18979
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
18980
+ drawProjectedOrganicMouth(context, [
18981
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
18982
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
18983
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
18984
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
18985
+ },
18986
+ };
18987
+ /**
18988
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
18989
+ *
18990
+ * @private helper of `octopus3d4AvatarVisual`
18991
+ */
18992
+ function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
18993
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
18994
+ const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
18995
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
18996
+ return {
18997
+ centerLongitude: -Math.PI * 0.9 +
18998
+ progress * Math.PI * 1.8 +
18999
+ (tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
19000
+ widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
19001
+ lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
19002
+ swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
19003
+ depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
19004
+ curlScale: 0.62 + tentacleRandom() * 0.46,
19005
+ primaryPhase: tentacleRandom() * Math.PI * 2,
19006
+ secondaryPhase: tentacleRandom() * Math.PI * 2,
19007
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
19008
+ };
19009
+ });
19010
+ }
19011
+ /**
19012
+ * Creates seeded skin pigment spots distributed across the upper octopus mesh.
19013
+ *
19014
+ * @private helper of `octopus3d4AvatarVisual`
19015
+ */
19016
+ function createBlobbySkinSpots(createRandom) {
19017
+ const spotRandom = createRandom('octopus3d4-skin-spots');
19018
+ return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
19019
+ latitude: -0.45 + spotRandom() * 0.7,
19020
+ longitude: -0.5 + spotRandom() * 1.0,
19021
+ radiusScale: 0.0028 + spotRandom() * 0.0052,
19022
+ opacity: 0.16 + spotRandom() * 0.22,
19023
+ }));
19024
+ }
19025
+ /**
19026
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
19027
+ *
19028
+ * @private helper of `octopus3d4AvatarVisual`
19029
+ */
19030
+ function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
19031
+ 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));
19032
+ glowGradient.addColorStop(0, `${palette.highlight}74`);
19033
+ glowGradient.addColorStop(0.32, `${palette.accent}32`);
19034
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
19035
+ context.fillStyle = glowGradient;
19036
+ context.fillRect(0, 0, size, size);
19037
+ 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);
19038
+ lowerGradient.addColorStop(0, `${palette.secondary}28`);
19039
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
19040
+ context.fillStyle = lowerGradient;
19041
+ context.fillRect(0, 0, size, size);
19042
+ }
19043
+ /**
19044
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
19045
+ *
19046
+ * @private helper of `octopus3d4AvatarVisual`
19047
+ */
19048
+ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
19049
+ const cx = size * 0.5 + interaction.gazeX * size * 0.046;
19050
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
19051
+ const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
19052
+ const ry = size * 0.062;
19053
+ context.save();
19054
+ context.translate(cx, cy);
19055
+ context.scale(1, ry / rx);
19056
+ const blurRadius = rx * 1.42;
19057
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
19058
+ shadowGradient.addColorStop(0, `${palette.shadow}82`);
19059
+ shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
19060
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
19061
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
19062
+ context.fillStyle = shadowGradient;
19063
+ context.beginPath();
19064
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
19065
+ context.fill();
19066
+ context.restore();
19067
+ }
19068
+ /**
19069
+ * Number of latitude segments used by the continuous Octopus 3D 4 mesh.
19070
+ *
19071
+ * @private helper of `octopus3d4AvatarVisual`
19072
+ */
19073
+ const LATITUDE_PATCH_COUNT = 20;
19074
+ /**
19075
+ * Number of longitude segments used by the continuous Octopus 3D 4 mesh.
19076
+ *
19077
+ * @private helper of `octopus3d4AvatarVisual`
19078
+ */
19079
+ const LONGITUDE_PATCH_COUNT = 48;
19080
+ /**
19081
+ * Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
19082
+ *
19083
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
19084
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
19085
+ * than re-evaluated for every patch corner.
19086
+ *
19087
+ * @private helper of `octopus3d4AvatarVisual`
19088
+ */
19089
+ function resolveVisibleBlobbyContinuousPatches(options) {
19090
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
19091
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
19092
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
19093
+ const surfacePatches = [];
19094
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
19095
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
19096
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
19097
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
19098
+ }
19099
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
19100
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
19101
+ }
19102
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
19103
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
19104
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
19105
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
19106
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
19107
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
19108
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
19109
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
19110
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
19111
+ }
19112
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
19113
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
19114
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
19115
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
19116
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
19117
+ }
19118
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
19119
+ const transformedCornerSamples = new Array(cornerCount);
19120
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
19121
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
19122
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
19123
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
19124
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
19125
+ const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
19126
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
19127
+ }
19128
+ }
19129
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
19130
+ const startLatitude = latitudeBoundaries[latitudeIndex];
19131
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
19132
+ const centerLatitude = (startLatitude + endLatitude) / 2;
19133
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
19134
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
19135
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
19136
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
19137
+ const transformedCorners = [
19138
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
19139
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
19140
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
19141
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
19142
+ ];
19143
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
19144
+ if (surfaceNormal.z <= 0.006) {
19145
+ continue;
19146
+ }
19147
+ const projectedCorners = [
19148
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
19149
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
19150
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
19151
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
19152
+ ];
19153
+ const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
19154
+ const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
19155
+ surfacePatches.push({
19156
+ corners: projectedCorners,
19157
+ averageDepth: (transformedCorners[0].z +
19158
+ transformedCorners[1].z +
19159
+ transformedCorners[2].z +
19160
+ transformedCorners[3].z) /
19161
+ 4,
19162
+ lightIntensity,
19163
+ rimLightIntensity,
19164
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
19165
+ outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
19166
+ });
19167
+ }
19168
+ }
19169
+ return surfacePatches;
19170
+ }
19171
+ /**
19172
+ * Samples one point on the continuous Octopus 3D 4 surface.
19173
+ *
19174
+ * @private helper of `octopus3d4AvatarVisual`
19175
+ */
19176
+ function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
19177
+ return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
19178
+ }
19179
+ /**
19180
+ * Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
19181
+ *
19182
+ * The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
19183
+ * across every latitude row and each tentacle/lobe value is computed once per frame instead
19184
+ * of `latitudePatchCount * 4` times.
19185
+ *
19186
+ * @private helper of `octopus3d4AvatarVisual`
19187
+ */
19188
+ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
19189
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
19190
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
19191
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
19192
+ const upperBlend = Math.pow(1 - verticalProgress, 1.32);
19193
+ const lowerBlend = smoothStep(0.34, 1, verticalProgress);
19194
+ const tipBlend = smoothStep(0.66, 1, verticalProgress);
19195
+ const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
19196
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
19197
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
19198
+ animationPhase * 0.6 +
19199
+ timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
19200
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
19201
+ (0.32 + lowerBlend * 0.7);
19202
+ const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
19203
+ lowerBlend *
19204
+ tentacleInfluence.core *
19205
+ tentacleInfluence.swayScale;
19206
+ const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
19207
+ lowerBlend *
19208
+ tentacleInfluence.core *
19209
+ tentacleInfluence.swayScale *
19210
+ 0.42;
19211
+ const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
19212
+ tipBlend *
19213
+ tentacleInfluence.core *
19214
+ tentacleInfluence.curlScale *
19215
+ 0.36;
19216
+ const horizontalScale = 1.06 +
19217
+ mantleRipple +
19218
+ lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
19219
+ lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
19220
+ upperBlend * 0.08;
19221
+ const depthScale = 1.08 +
19222
+ upperBlend * 0.16 +
19223
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
19224
+ lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
19225
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
19226
+ const tentacleTubeRadius = lowerBlend *
19227
+ tentacleInfluence.core *
19228
+ (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
19229
+ radiusX;
19230
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
19231
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
19232
+ const lowerDrop = lowerBlend *
19233
+ radiusY *
19234
+ (0.2 +
19235
+ tentacleInfluence.core *
19236
+ (0.42 +
19237
+ tentacleInfluence.lengthScale * 0.24 +
19238
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
19239
+ const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
19240
+ return {
19241
+ x: Math.sin(effectiveLongitude) * planarRadiusX +
19242
+ combinedTentacleSway +
19243
+ tentacleCurl * radiusX * 0.18,
19244
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
19245
+ upperBlend * radiusY * 0.12 +
19246
+ lowerDrop +
19247
+ Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
19248
+ Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
19249
+ lowerBlend *
19250
+ tipBlend *
19251
+ tentacleInfluence.core *
19252
+ radiusY *
19253
+ 0.04,
19254
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
19255
+ Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
19256
+ lowerBlend *
19257
+ tentacleInfluence.core *
19258
+ radiusZ *
19259
+ 0.044 +
19260
+ tentacleCurl * radiusZ * 0.14,
19261
+ };
19262
+ }
19263
+ /**
19264
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
19265
+ *
19266
+ * @private helper of `octopus3d4AvatarVisual`
19267
+ */
19268
+ function resolveBlobbyTentacleInfluence(options, longitude) {
19269
+ let totalWeight = 0;
19270
+ let weightedSin = 0;
19271
+ let weightedCos = 0;
19272
+ let weightedWidthScale = 0;
19273
+ let weightedLengthScale = 0;
19274
+ let weightedSwayScale = 0;
19275
+ let weightedDepthScale = 0;
19276
+ let weightedCurlScale = 0;
19277
+ let weightedPrimaryPhase = 0;
19278
+ let weightedSecondaryPhase = 0;
19279
+ for (const tentacleProfile of options.tentacleProfiles) {
19280
+ const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
19281
+ const width = 0.22 * tentacleProfile.widthScale;
19282
+ const weight = Math.exp(-(distance * distance) / (width * width));
19283
+ totalWeight += weight;
19284
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
19285
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
19286
+ weightedWidthScale += tentacleProfile.widthScale * weight;
19287
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
19288
+ weightedSwayScale += tentacleProfile.swayScale * weight;
19289
+ weightedDepthScale += tentacleProfile.depthScale * weight;
19290
+ weightedCurlScale += tentacleProfile.curlScale * weight;
19291
+ weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
19292
+ weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
19293
+ }
19294
+ if (totalWeight < 0.0001) {
19295
+ return {
19296
+ core: 0,
19297
+ centerLongitude: longitude,
19298
+ widthScale: 1,
19299
+ lengthScale: 1,
19300
+ swayScale: 1,
19301
+ depthScale: 1,
19302
+ curlScale: 1,
19303
+ primaryPhase: 0,
19304
+ secondaryPhase: 0,
19305
+ };
19306
+ }
19307
+ return {
19308
+ core: clampNumber$1(totalWeight, 0, 1),
19309
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
19310
+ widthScale: weightedWidthScale / totalWeight,
19311
+ lengthScale: weightedLengthScale / totalWeight,
19312
+ swayScale: weightedSwayScale / totalWeight,
19313
+ depthScale: weightedDepthScale / totalWeight,
19314
+ curlScale: weightedCurlScale / totalWeight,
19315
+ primaryPhase: weightedPrimaryPhase / totalWeight,
19316
+ secondaryPhase: weightedSecondaryPhase / totalWeight,
19317
+ };
19318
+ }
19319
+ /**
19320
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
19321
+ *
19322
+ * @private helper of `octopus3d4AvatarVisual`
19323
+ */
19324
+ function resolveBlobbyLobeWave(options, longitude) {
19325
+ const { morphologyProfile, animationPhase, timeMs } = options;
19326
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
19327
+ animationPhase +
19328
+ timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
19329
+ 1) /
19330
+ 2);
19331
+ }
19332
+ /**
19333
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
19334
+ *
19335
+ * @private helper of `octopus3d4AvatarVisual`
19336
+ */
19337
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
19338
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
19339
+ if (tonalProgress < 0.12) {
19340
+ return palette.highlight;
19341
+ }
19342
+ if (tonalProgress < 0.3) {
19343
+ return palette.secondary;
19344
+ }
19345
+ if (tonalProgress < 0.72) {
19346
+ return forwardness > 0.56 ? palette.secondary : palette.primary;
19347
+ }
19348
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
19349
+ }
19350
+ /**
19351
+ * Draws one projected mesh patch with multi-pass shading and a soft edge.
19352
+ *
19353
+ * @private helper of `octopus3d4AvatarVisual`
19354
+ */
19355
+ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
19356
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
19357
+ if (surfacePatch.lightIntensity > 0) {
19358
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
19359
+ }
19360
+ else if (surfacePatch.lightIntensity < 0) {
19361
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
19362
+ }
19363
+ if (surfacePatch.rimLightIntensity > 0.04) {
19364
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
19365
+ }
19366
+ context.save();
19367
+ context.beginPath();
19368
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
19369
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
19370
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
19371
+ }
19372
+ context.closePath();
19373
+ context.strokeStyle = surfacePatch.outlineColor;
19374
+ context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
19375
+ context.lineJoin = 'round';
19376
+ context.stroke();
19377
+ context.restore();
19378
+ }
19379
+ /**
19380
+ * Draws projected mantle-current lines on the front of the mesh.
19381
+ *
19382
+ * @private helper of `octopus3d4AvatarVisual`
19383
+ */
19384
+ function drawBlobbyContinuousCurrents(options) {
19385
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
19386
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
19387
+ const centerIndex = (currentCount - 1) / 2;
19388
+ context.save();
19389
+ context.lineCap = 'round';
19390
+ context.lineJoin = 'round';
19391
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
19392
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
19393
+ const projectedPoints = [];
19394
+ for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
19395
+ const progress = sampleIndex / 8;
19396
+ const latitude = -0.48 + progress * 0.78;
19397
+ const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
19398
+ const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
19399
+ if (scenePoint.z > center.z - size * 0.016) {
19400
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
19401
+ }
19402
+ }
19403
+ if (projectedPoints.length < 3) {
19404
+ continue;
19405
+ }
19406
+ context.beginPath();
19407
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
19408
+ for (const projectedPoint of projectedPoints.slice(1)) {
19409
+ context.lineTo(projectedPoint.x, projectedPoint.y);
19410
+ }
19411
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
19412
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
19413
+ context.stroke();
19414
+ }
19415
+ context.restore();
19416
+ }
19417
+ /**
19418
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
19419
+ *
19420
+ * @private helper of `octopus3d4AvatarVisual`
19421
+ */
19422
+ function drawBlobbyContinuousSuckers(options) {
19423
+ const { surfaceOptions, size } = options;
19424
+ const { timeMs } = surfaceOptions;
19425
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
19426
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
19427
+ continue;
19428
+ }
19429
+ for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
19430
+ const latitude = 0.5 + suckerIndex * 0.12;
19431
+ const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
19432
+ const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
19433
+ drawBlobbyContinuousSurfaceSpot({
19434
+ ...options,
19435
+ latitude,
19436
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
19437
+ radiusScale: size * (0.0068 - suckerIndex * 0.0006),
19438
+ });
19439
+ }
19440
+ }
19441
+ }
19442
+ /**
19443
+ * Draws seeded pigment spots across the upper mesh for a richer skin texture.
19444
+ *
19445
+ * @private helper of `octopus3d4AvatarVisual`
19446
+ */
19447
+ function drawBlobbySkinSpots(options) {
19448
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
19449
+ for (const skinSpot of skinSpots) {
19450
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
19451
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
19452
+ if (sceneCenterPoint.z <= center.z - size * 0.01) {
19453
+ continue;
19454
+ }
19455
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
19456
+ const spotRadius = size * skinSpot.radiusScale;
19457
+ context.save();
19458
+ context.beginPath();
19459
+ context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
19460
+ context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
19461
+ context.fill();
19462
+ context.restore();
19463
+ }
19464
+ }
19465
+ /**
19466
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
19467
+ *
19468
+ * @private helper of `octopus3d4AvatarVisual`
19469
+ */
19470
+ function drawBlobbyContinuousSurfaceSpot(options) {
19471
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
19472
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
19473
+ const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
19474
+ const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
19475
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
19476
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
19477
+ return;
19478
+ }
19479
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
19480
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
19481
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
19482
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
19483
+ radiusScale *
19484
+ 0.78, size * 0.003, size * 0.02);
19485
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
19486
+ radiusScale *
19487
+ 0.54, size * 0.0024, size * 0.015);
19488
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
19489
+ context.save();
19490
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
19491
+ context.rotate(rotation);
19492
+ context.beginPath();
19493
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
19494
+ context.fillStyle = `${palette.highlight}80`;
19495
+ context.fill();
19496
+ context.strokeStyle = `${palette.highlight}a8`;
19497
+ context.lineWidth = Math.max(0.7, size * 0.0028);
19498
+ context.stroke();
19499
+ context.restore();
19500
+ }
19501
+ /**
19502
+ * Draws a soft, slowly drifting gloss highlight on the front of the mesh.
19503
+ *
19504
+ * @private helper of `octopus3d4AvatarVisual`
19505
+ */
19506
+ function drawBlobbyContinuousGloss(options) {
19507
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
19508
+ const { timeMs, animationPhase } = surfaceOptions;
19509
+ const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
19510
+ const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
19511
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
19512
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
19513
+ if (sceneCenterPoint.z <= center.z) {
19514
+ return;
19515
+ }
19516
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
19517
+ const glossRadius = size * 0.058;
19518
+ context.save();
19519
+ const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
19520
+ glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
19521
+ glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
19522
+ glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
19523
+ context.fillStyle = glossGradient;
19524
+ context.beginPath();
19525
+ context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
19526
+ context.fill();
19527
+ context.restore();
19528
+ }
19529
+ /**
19530
+ * Resolves a signed angular distance from the source longitude to the target longitude.
19531
+ *
19532
+ * @private helper of `octopus3d4AvatarVisual`
19533
+ */
19534
+ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
19535
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
19536
+ }
19537
+ /**
19538
+ * Smoothly maps a value between two bounds into `[0, 1]`.
19539
+ *
19540
+ * @private helper of `octopus3d4AvatarVisual`
19541
+ */
19542
+ function smoothStep(edgeStart, edgeEnd, value) {
19543
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
19544
+ return progress * progress * (3 - 2 * progress);
19545
+ }
19546
+ /**
19547
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
19548
+ *
19549
+ * @private helper of `octopus3d4AvatarVisual`
19550
+ */
19551
+ function formatAlphaHex(opacity) {
19552
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
19553
+ .toString(16)
19554
+ .padStart(2, '0');
19555
+ }
19556
+
18802
19557
  /* eslint-disable no-magic-numbers */
18803
19558
  /**
18804
19559
  * Octopus avatar visual.
@@ -19570,6 +20325,7 @@ const AVATAR_VISUALS = [
19570
20325
  octopus3dAvatarVisual,
19571
20326
  octopus3d2AvatarVisual,
19572
20327
  octopus3d3AvatarVisual,
20328
+ octopus3d4AvatarVisual,
19573
20329
  asciiOctopusAvatarVisual,
19574
20330
  minecraftAvatarVisual,
19575
20331
  minecraft2AvatarVisual,