@promptbook/components 0.112.0-69 → 0.112.0-70

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 (27) hide show
  1. package/esm/index.es.js +753 -221
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  4. package/esm/src/avatars/visuals/avatar3dProjectionShared.d.ts +141 -0
  5. package/esm/src/avatars/visuals/octopus3dAvatarVisual.d.ts +7 -0
  6. package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +2 -2
  7. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +22 -0
  8. package/esm/src/cli/cli-commands/agent/runMultiple.d.ts +10 -0
  9. package/esm/src/cli/cli-commands/agent.d.ts +3 -2
  10. package/esm/src/cli/other/install.test.d.ts +1 -0
  11. package/esm/src/commitments/USE_TIMEOUT/USE_TIMEOUT.d.ts +1 -1
  12. package/esm/src/commitments/WALLET/WALLET.d.ts +1 -1
  13. package/esm/src/version.d.ts +1 -1
  14. package/package.json +1 -1
  15. package/umd/index.umd.js +753 -221
  16. package/umd/index.umd.js.map +1 -1
  17. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  18. package/umd/src/avatars/visuals/avatar3dProjectionShared.d.ts +141 -0
  19. package/umd/src/avatars/visuals/octopus3dAvatarVisual.d.ts +7 -0
  20. package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +2 -2
  21. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +22 -0
  22. package/umd/src/cli/cli-commands/agent/runMultiple.d.ts +10 -0
  23. package/umd/src/cli/cli-commands/agent.d.ts +3 -2
  24. package/umd/src/cli/other/install.test.d.ts +1 -0
  25. package/umd/src/commitments/USE_TIMEOUT/USE_TIMEOUT.d.ts +1 -1
  26. package/umd/src/commitments/WALLET/WALLET.d.ts +1 -1
  27. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -40,7 +40,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
40
40
  * @generated
41
41
  * @see https://github.com/webgptorg/promptbook
42
42
  */
43
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-69';
43
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-70';
44
44
  /**
45
45
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
46
46
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2854,6 +2854,184 @@ function drawFractalCore(context, size, palette, timeMs, corePhase) {
2854
2854
  context.restore();
2855
2855
  }
2856
2856
 
2857
+ /* eslint-disable no-magic-numbers */
2858
+ /**
2859
+ * Default camera distance ratio shared by the proper-3D avatar visuals.
2860
+ *
2861
+ * @private helper of the 3D avatar visuals
2862
+ */
2863
+ const DEFAULT_3D_CAMERA_DISTANCE_RATIO = 1.4;
2864
+ /**
2865
+ * Clamps one number into the provided range.
2866
+ *
2867
+ * @param value Input value.
2868
+ * @param minimumValue Inclusive lower bound.
2869
+ * @param maximumValue Inclusive upper bound.
2870
+ * @returns Clamped value.
2871
+ *
2872
+ * @private helper of the 3D avatar visuals
2873
+ */
2874
+ function clampNumber$1(value, minimumValue, maximumValue) {
2875
+ return Math.min(maximumValue, Math.max(minimumValue, value));
2876
+ }
2877
+ /**
2878
+ * Rotates one point around the local Y axis.
2879
+ *
2880
+ * @param point Source point.
2881
+ * @param angle Rotation angle in radians.
2882
+ * @returns Rotated point.
2883
+ *
2884
+ * @private helper of the 3D avatar visuals
2885
+ */
2886
+ function rotatePointAroundY(point, angle) {
2887
+ const cosine = Math.cos(angle);
2888
+ const sine = Math.sin(angle);
2889
+ return {
2890
+ x: point.x * cosine + point.z * sine,
2891
+ y: point.y,
2892
+ z: -point.x * sine + point.z * cosine,
2893
+ };
2894
+ }
2895
+ /**
2896
+ * Rotates one point around the local X axis.
2897
+ *
2898
+ * @param point Source point.
2899
+ * @param angle Rotation angle in radians.
2900
+ * @returns Rotated point.
2901
+ *
2902
+ * @private helper of the 3D avatar visuals
2903
+ */
2904
+ function rotatePointAroundX(point, angle) {
2905
+ const cosine = Math.cos(angle);
2906
+ const sine = Math.sin(angle);
2907
+ return {
2908
+ x: point.x,
2909
+ y: point.y * cosine - point.z * sine,
2910
+ z: point.y * sine + point.z * cosine,
2911
+ };
2912
+ }
2913
+ /**
2914
+ * Applies the local rotations and translation to one scene point.
2915
+ *
2916
+ * @param localPoint Point in local object space.
2917
+ * @param center Object center in scene space.
2918
+ * @param rotationX Object pitch in radians.
2919
+ * @param rotationY Object yaw in radians.
2920
+ * @returns Transformed scene-space point.
2921
+ *
2922
+ * @private helper of the 3D avatar visuals
2923
+ */
2924
+ function transformScenePoint(localPoint, center, rotationX, rotationY) {
2925
+ const yawedPoint = rotatePointAroundY(localPoint, rotationY);
2926
+ const pitchedPoint = rotatePointAroundX(yawedPoint, rotationX);
2927
+ return {
2928
+ x: center.x + pitchedPoint.x,
2929
+ y: center.y + pitchedPoint.y,
2930
+ z: center.z + pitchedPoint.z,
2931
+ };
2932
+ }
2933
+ /**
2934
+ * Projects one scene point into canvas coordinates.
2935
+ *
2936
+ * @param point Scene-space point.
2937
+ * @param size Canvas size in CSS pixels.
2938
+ * @param sceneCenterX Horizontal scene center.
2939
+ * @param sceneCenterY Vertical scene center.
2940
+ * @param cameraDistanceRatio Optional camera distance ratio.
2941
+ * @returns Projected point.
2942
+ *
2943
+ * @private helper of the 3D avatar visuals
2944
+ */
2945
+ function projectScenePoint(point, size, sceneCenterX, sceneCenterY, cameraDistanceRatio = DEFAULT_3D_CAMERA_DISTANCE_RATIO) {
2946
+ const cameraDistance = size * cameraDistanceRatio;
2947
+ const perspectiveScale = cameraDistance / Math.max(cameraDistance - point.z, cameraDistance * 0.35);
2948
+ return {
2949
+ x: sceneCenterX + point.x * perspectiveScale,
2950
+ y: sceneCenterY + point.y * perspectiveScale,
2951
+ z: point.z,
2952
+ };
2953
+ }
2954
+ /**
2955
+ * Subtracts one 3D point from another.
2956
+ *
2957
+ * @param leftPoint Left point.
2958
+ * @param rightPoint Right point.
2959
+ * @returns Difference vector.
2960
+ *
2961
+ * @private helper of the 3D avatar visuals
2962
+ */
2963
+ function subtractPoint3D(leftPoint, rightPoint) {
2964
+ return {
2965
+ x: leftPoint.x - rightPoint.x,
2966
+ y: leftPoint.y - rightPoint.y,
2967
+ z: leftPoint.z - rightPoint.z,
2968
+ };
2969
+ }
2970
+ /**
2971
+ * Computes the 3D cross product of two vectors.
2972
+ *
2973
+ * @param leftVector Left vector.
2974
+ * @param rightVector Right vector.
2975
+ * @returns Cross product.
2976
+ *
2977
+ * @private helper of the 3D avatar visuals
2978
+ */
2979
+ function crossProduct3D(leftVector, rightVector) {
2980
+ return {
2981
+ x: leftVector.y * rightVector.z - leftVector.z * rightVector.y,
2982
+ y: leftVector.z * rightVector.x - leftVector.x * rightVector.z,
2983
+ z: leftVector.x * rightVector.y - leftVector.y * rightVector.x,
2984
+ };
2985
+ }
2986
+ /**
2987
+ * Computes the 3D dot product of two vectors.
2988
+ *
2989
+ * @param leftVector Left vector.
2990
+ * @param rightVector Right vector.
2991
+ * @returns Dot product.
2992
+ *
2993
+ * @private helper of the 3D avatar visuals
2994
+ */
2995
+ function dotProduct3D(leftVector, rightVector) {
2996
+ return leftVector.x * rightVector.x + leftVector.y * rightVector.y + leftVector.z * rightVector.z;
2997
+ }
2998
+ /**
2999
+ * Normalizes one 3D vector while keeping zero vectors stable.
3000
+ *
3001
+ * @param vector Source vector.
3002
+ * @returns Normalized vector.
3003
+ *
3004
+ * @private helper of the 3D avatar visuals
3005
+ */
3006
+ function normalizeVector3(vector) {
3007
+ const length = Math.hypot(vector.x, vector.y, vector.z);
3008
+ if (length === 0) {
3009
+ return vector;
3010
+ }
3011
+ return {
3012
+ x: vector.x / length,
3013
+ y: vector.y / length,
3014
+ z: vector.z / length,
3015
+ };
3016
+ }
3017
+ /**
3018
+ * Measures the perimeter of one projected quad.
3019
+ *
3020
+ * @param corners Quad corners.
3021
+ * @returns Perimeter length.
3022
+ *
3023
+ * @private helper of the 3D avatar visuals
3024
+ */
3025
+ function getProjectedQuadPerimeter(corners) {
3026
+ let perimeter = 0;
3027
+ for (let cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
3028
+ const currentCorner = corners[cornerIndex];
3029
+ const nextCorner = corners[(cornerIndex + 1) % corners.length];
3030
+ perimeter += Math.hypot(nextCorner.x - currentCorner.x, nextCorner.y - currentCorner.y);
3031
+ }
3032
+ return perimeter;
3033
+ }
3034
+
2857
3035
  /* eslint-disable no-magic-numbers */
2858
3036
  /**
2859
3037
  * Builds the seeded six-face texture pack used by the Minecraft-style head cuboid.
@@ -3026,18 +3204,12 @@ function fillTextureRect(texture, x, y, width, height, color) {
3026
3204
  }
3027
3205
 
3028
3206
  /* eslint-disable no-magic-numbers */
3029
- /**
3030
- * Fixed scene camera distance used for the proper-3D projection.
3031
- *
3032
- * @private helper of `minecraft2AvatarVisual`
3033
- */
3034
- const CAMERA_DISTANCE_RATIO = 1.4;
3035
3207
  /**
3036
3208
  * Shared light direction used to shade projected cuboid faces.
3037
3209
  *
3038
3210
  * @private helper of `minecraft2AvatarVisual`
3039
3211
  */
3040
- const LIGHT_DIRECTION = normalizeVector3({
3212
+ const LIGHT_DIRECTION$1 = normalizeVector3({
3041
3213
  x: 0.4,
3042
3214
  y: -0.65,
3043
3215
  z: 0.92,
@@ -3249,7 +3421,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
3249
3421
  corners: projectedCorners,
3250
3422
  texture: faceDefinition.texture,
3251
3423
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
3252
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION), -1, 1),
3424
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
3253
3425
  outlineColor: cuboid.outlineColor,
3254
3426
  };
3255
3427
  });
@@ -3276,7 +3448,7 @@ function drawTexturedProjectedFace(context, face) {
3276
3448
  const endX = (columnIndex + 1) / columns;
3277
3449
  const startY = rowIndex / rows;
3278
3450
  const endY = (rowIndex + 1) / rows;
3279
- drawProjectedQuad(context, [
3451
+ drawProjectedQuad$1(context, [
3280
3452
  interpolateProjectedQuad(face.corners, startX, startY),
3281
3453
  interpolateProjectedQuad(face.corners, endX, startY),
3282
3454
  interpolateProjectedQuad(face.corners, endX, endY),
@@ -3285,10 +3457,10 @@ function drawTexturedProjectedFace(context, face) {
3285
3457
  }
3286
3458
  }
3287
3459
  if (face.lightIntensity > 0) {
3288
- drawProjectedQuad(context, face.corners, `rgba(255, 255, 255, ${0.15 * face.lightIntensity})`);
3460
+ drawProjectedQuad$1(context, face.corners, `rgba(255, 255, 255, ${0.15 * face.lightIntensity})`);
3289
3461
  }
3290
3462
  else if (face.lightIntensity < 0) {
3291
- drawProjectedQuad(context, face.corners, `rgba(0, 0, 0, ${0.22 * Math.abs(face.lightIntensity)})`);
3463
+ drawProjectedQuad$1(context, face.corners, `rgba(0, 0, 0, ${0.22 * Math.abs(face.lightIntensity)})`);
3292
3464
  }
3293
3465
  context.save();
3294
3466
  context.beginPath();
@@ -3312,7 +3484,7 @@ function drawTexturedProjectedFace(context, face) {
3312
3484
  *
3313
3485
  * @private helper of `minecraft2AvatarVisual`
3314
3486
  */
3315
- function drawProjectedQuad(context, corners, fillStyle) {
3487
+ function drawProjectedQuad$1(context, corners, fillStyle) {
3316
3488
  context.beginPath();
3317
3489
  context.moveTo(corners[0].x, corners[0].y);
3318
3490
  context.lineTo(corners[1].x, corners[1].y);
@@ -3354,175 +3526,6 @@ function interpolateProjectedPoint(startPoint, endPoint, ratio) {
3354
3526
  z: startPoint.z + (endPoint.z - startPoint.z) * ratio,
3355
3527
  };
3356
3528
  }
3357
- /**
3358
- * Projects one rotated scene point into canvas coordinates.
3359
- *
3360
- * @param point Scene point.
3361
- * @param size Canvas size in CSS pixels.
3362
- * @param sceneCenterX Horizontal scene center.
3363
- * @param sceneCenterY Vertical scene center.
3364
- * @returns Projected point.
3365
- *
3366
- * @private helper of `minecraft2AvatarVisual`
3367
- */
3368
- function projectScenePoint(point, size, sceneCenterX, sceneCenterY) {
3369
- const cameraDistance = size * CAMERA_DISTANCE_RATIO;
3370
- const perspectiveScale = cameraDistance / Math.max(cameraDistance - point.z, cameraDistance * 0.35);
3371
- return {
3372
- x: sceneCenterX + point.x * perspectiveScale,
3373
- y: sceneCenterY + point.y * perspectiveScale,
3374
- z: point.z,
3375
- };
3376
- }
3377
- /**
3378
- * Applies the local cuboid rotations and translation to one scene point.
3379
- *
3380
- * @param localPoint Point in cuboid-local space.
3381
- * @param center Cuboid center in scene space.
3382
- * @param rotationX Cuboid pitch in radians.
3383
- * @param rotationY Cuboid yaw in radians.
3384
- * @returns Transformed scene-space point.
3385
- *
3386
- * @private helper of `minecraft2AvatarVisual`
3387
- */
3388
- function transformScenePoint(localPoint, center, rotationX, rotationY) {
3389
- const yawedPoint = rotatePointAroundY(localPoint, rotationY);
3390
- const pitchedPoint = rotatePointAroundX(yawedPoint, rotationX);
3391
- return {
3392
- x: center.x + pitchedPoint.x,
3393
- y: center.y + pitchedPoint.y,
3394
- z: center.z + pitchedPoint.z,
3395
- };
3396
- }
3397
- /**
3398
- * Rotates one point around the local Y axis.
3399
- *
3400
- * @param point Source point.
3401
- * @param angle Rotation angle in radians.
3402
- * @returns Rotated point.
3403
- *
3404
- * @private helper of `minecraft2AvatarVisual`
3405
- */
3406
- function rotatePointAroundY(point, angle) {
3407
- const cosine = Math.cos(angle);
3408
- const sine = Math.sin(angle);
3409
- return {
3410
- x: point.x * cosine + point.z * sine,
3411
- y: point.y,
3412
- z: -point.x * sine + point.z * cosine,
3413
- };
3414
- }
3415
- /**
3416
- * Rotates one point around the local X axis.
3417
- *
3418
- * @param point Source point.
3419
- * @param angle Rotation angle in radians.
3420
- * @returns Rotated point.
3421
- *
3422
- * @private helper of `minecraft2AvatarVisual`
3423
- */
3424
- function rotatePointAroundX(point, angle) {
3425
- const cosine = Math.cos(angle);
3426
- const sine = Math.sin(angle);
3427
- return {
3428
- x: point.x,
3429
- y: point.y * cosine - point.z * sine,
3430
- z: point.y * sine + point.z * cosine,
3431
- };
3432
- }
3433
- /**
3434
- * Subtracts one 3D point from another.
3435
- *
3436
- * @param leftPoint Left point.
3437
- * @param rightPoint Right point.
3438
- * @returns Difference vector.
3439
- *
3440
- * @private helper of `minecraft2AvatarVisual`
3441
- */
3442
- function subtractPoint3D(leftPoint, rightPoint) {
3443
- return {
3444
- x: leftPoint.x - rightPoint.x,
3445
- y: leftPoint.y - rightPoint.y,
3446
- z: leftPoint.z - rightPoint.z,
3447
- };
3448
- }
3449
- /**
3450
- * Computes the 3D cross product of two vectors.
3451
- *
3452
- * @param leftVector Left vector.
3453
- * @param rightVector Right vector.
3454
- * @returns Cross product.
3455
- *
3456
- * @private helper of `minecraft2AvatarVisual`
3457
- */
3458
- function crossProduct3D(leftVector, rightVector) {
3459
- return {
3460
- x: leftVector.y * rightVector.z - leftVector.z * rightVector.y,
3461
- y: leftVector.z * rightVector.x - leftVector.x * rightVector.z,
3462
- z: leftVector.x * rightVector.y - leftVector.y * rightVector.x,
3463
- };
3464
- }
3465
- /**
3466
- * Computes the 3D dot product of two vectors.
3467
- *
3468
- * @param leftVector Left vector.
3469
- * @param rightVector Right vector.
3470
- * @returns Dot product.
3471
- *
3472
- * @private helper of `minecraft2AvatarVisual`
3473
- */
3474
- function dotProduct3D(leftVector, rightVector) {
3475
- return leftVector.x * rightVector.x + leftVector.y * rightVector.y + leftVector.z * rightVector.z;
3476
- }
3477
- /**
3478
- * Normalizes one 3D vector while keeping zero vectors stable.
3479
- *
3480
- * @param vector Source vector.
3481
- * @returns Normalized vector.
3482
- *
3483
- * @private helper of `minecraft2AvatarVisual`
3484
- */
3485
- function normalizeVector3(vector) {
3486
- const length = Math.hypot(vector.x, vector.y, vector.z);
3487
- if (length === 0) {
3488
- return vector;
3489
- }
3490
- return {
3491
- x: vector.x / length,
3492
- y: vector.y / length,
3493
- z: vector.z / length,
3494
- };
3495
- }
3496
- /**
3497
- * Clamps one number into the provided range.
3498
- *
3499
- * @param value Input value.
3500
- * @param minimumValue Inclusive lower bound.
3501
- * @param maximumValue Inclusive upper bound.
3502
- * @returns Clamped value.
3503
- *
3504
- * @private helper of `minecraft2AvatarVisual`
3505
- */
3506
- function clampNumber$1(value, minimumValue, maximumValue) {
3507
- return Math.min(maximumValue, Math.max(minimumValue, value));
3508
- }
3509
- /**
3510
- * Measures the perimeter of one projected quad.
3511
- *
3512
- * @param corners Quad corners.
3513
- * @returns Perimeter length.
3514
- *
3515
- * @private helper of `minecraft2AvatarVisual`
3516
- */
3517
- function getProjectedQuadPerimeter(corners) {
3518
- let perimeter = 0;
3519
- for (let cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
3520
- const currentCorner = corners[cornerIndex];
3521
- const nextCorner = corners[(cornerIndex + 1) % corners.length];
3522
- perimeter += Math.hypot(nextCorner.x - currentCorner.x, nextCorner.y - currentCorner.y);
3523
- }
3524
- return perimeter;
3525
- }
3526
3529
 
3527
3530
  /* eslint-disable no-magic-numbers */
3528
3531
  /**
@@ -4091,7 +4094,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
4091
4094
  *
4092
4095
  * @private helper of `octopus3AvatarVisual`
4093
4096
  */
4094
- function formatAlphaHex(opacity) {
4097
+ function formatAlphaHex$1(opacity) {
4095
4098
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
4096
4099
  .toString(16)
4097
4100
  .padStart(2, '0');
@@ -4470,7 +4473,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
4470
4473
  context.beginPath();
4471
4474
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
4472
4475
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
4473
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
4476
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
4474
4477
  context.lineWidth = radiusX * 0.08;
4475
4478
  context.lineCap = 'round';
4476
4479
  context.stroke();
@@ -4478,6 +4481,518 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
4478
4481
  context.restore();
4479
4482
  }
4480
4483
 
4484
+ /* eslint-disable no-magic-numbers */
4485
+ /**
4486
+ * Light direction used by the organic 3D octopus shading.
4487
+ *
4488
+ * @private helper of `octopus3dAvatarVisual`
4489
+ */
4490
+ const LIGHT_DIRECTION = normalizeVector3({
4491
+ x: 0.48,
4492
+ y: -0.62,
4493
+ z: 0.94,
4494
+ });
4495
+ /**
4496
+ * Proper 3D Octopus visual built from projected organic meshes and tentacles.
4497
+ *
4498
+ * @private built-in avatar visual
4499
+ */
4500
+ const octopus3dAvatarVisual = {
4501
+ id: 'octopus3d',
4502
+ title: 'Octopus 3D',
4503
+ description: 'Proper 3D octopus portrait with a turning silhouette, expressive eyes, and depth-sorted tentacles.',
4504
+ isAnimated: true,
4505
+ supportsPointerTracking: true,
4506
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
4507
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
4508
+ const animationRandom = createRandom('octopus3d-animation-profile');
4509
+ const eyeRandom = createRandom('octopus3d-eye-profile');
4510
+ const animationPhase = animationRandom() * Math.PI * 2;
4511
+ const sceneCenterX = size * 0.5;
4512
+ const sceneCenterY = size * 0.56;
4513
+ const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
4514
+ const mantleCenter = {
4515
+ x: interaction.bodyOffsetX * size * 0.042 + size * morphologyProfile.body.centerXJitterRatio * 0.65,
4516
+ y: -size * 0.09 + interaction.bodyOffsetY * size * 0.028 + bob,
4517
+ z: interaction.intensity * size * 0.012,
4518
+ };
4519
+ const underbodyCenter = {
4520
+ x: mantleCenter.x * 0.86,
4521
+ y: mantleCenter.y + size * 0.168,
4522
+ z: mantleCenter.z - size * 0.018,
4523
+ };
4524
+ const mantleRadiusX = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch;
4525
+ const mantleRadiusY = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1;
4526
+ const mantleRadiusZ = size * morphologyProfile.body.bodyRadiusRatio * (0.9 + (morphologyProfile.body.horizontalStretch - 1) * 0.3);
4527
+ const underbodyRadiusX = mantleRadiusX * (0.9 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.08);
4528
+ const underbodyRadiusY = mantleRadiusY * (0.44 + morphologyProfile.body.lowerDropRatio * 3.1);
4529
+ const underbodyRadiusZ = mantleRadiusZ * 0.78;
4530
+ const bodyYaw = -0.18 +
4531
+ Math.sin(timeMs / 2400 + animationPhase) * 0.05 +
4532
+ interaction.bodyOffsetX * 0.18 +
4533
+ interaction.gazeX * 0.22;
4534
+ const bodyPitch = -0.08 +
4535
+ Math.cos(timeMs / 2700 + animationPhase * 0.6) * 0.025 -
4536
+ interaction.bodyOffsetY * 0.08 -
4537
+ interaction.gazeY * 0.08;
4538
+ const headYaw = bodyYaw - 0.04 + interaction.gazeX * 0.56;
4539
+ const headPitch = bodyPitch - 0.02 - interaction.gazeY * 0.32;
4540
+ const mantlePatches = resolveVisibleEllipsoidPatches({
4541
+ center: mantleCenter,
4542
+ radiusX: mantleRadiusX,
4543
+ radiusY: mantleRadiusY,
4544
+ radiusZ: mantleRadiusZ,
4545
+ rotationX: headPitch,
4546
+ rotationY: headYaw,
4547
+ sceneCenterX,
4548
+ sceneCenterY,
4549
+ size,
4550
+ palette,
4551
+ verticalColorBias: 0,
4552
+ outlineColor: `${palette.highlight}7a`,
4553
+ });
4554
+ const underbodyPatches = resolveVisibleEllipsoidPatches({
4555
+ center: underbodyCenter,
4556
+ radiusX: underbodyRadiusX,
4557
+ radiusY: underbodyRadiusY,
4558
+ radiusZ: underbodyRadiusZ,
4559
+ rotationX: bodyPitch,
4560
+ rotationY: bodyYaw,
4561
+ sceneCenterX,
4562
+ sceneCenterY,
4563
+ size,
4564
+ palette,
4565
+ verticalColorBias: 0.18,
4566
+ outlineColor: `${palette.shadow}8f`,
4567
+ });
4568
+ const tentacleStrokes = createOctopusTentacleStrokes({
4569
+ createRandom,
4570
+ morphologyProfile,
4571
+ timeMs,
4572
+ size,
4573
+ center: underbodyCenter,
4574
+ radiusX: underbodyRadiusX,
4575
+ radiusY: underbodyRadiusY,
4576
+ radiusZ: underbodyRadiusZ,
4577
+ rotationX: bodyPitch,
4578
+ rotationY: bodyYaw,
4579
+ sceneCenterX,
4580
+ sceneCenterY,
4581
+ animationPhase,
4582
+ });
4583
+ const faceEyeSpacing = size * morphologyProfile.face.eyeSpacingRatio * 0.92;
4584
+ const faceEyeYOffset = size * morphologyProfile.face.eyeCenterYOffsetRatio - mantleRadiusY * 0.02;
4585
+ const faceEyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.82;
4586
+ const faceEyeRadiusY = faceEyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.96;
4587
+ const mouthHalfWidth = size * morphologyProfile.face.mouthWidthRatio * 0.92;
4588
+ const mouthY = size * morphologyProfile.face.mouthYOffsetRatio + mantleRadiusY * 0.08;
4589
+ drawAvatarFrame(context, size, palette);
4590
+ drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
4591
+ drawOctopus3dShadow(context, size, palette, interaction, timeMs);
4592
+ for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => !candidateTentacleStroke.isFrontFacing)) {
4593
+ drawTentacleStroke(context, tentacleStroke, palette);
4594
+ }
4595
+ for (const surfacePatch of [...mantlePatches, ...underbodyPatches].sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
4596
+ drawSurfacePatch(context, surfacePatch);
4597
+ }
4598
+ for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
4599
+ drawTentacleStroke(context, tentacleStroke, palette);
4600
+ }
4601
+ drawProjectedEye(context, {
4602
+ x: -faceEyeSpacing,
4603
+ y: faceEyeYOffset,
4604
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
4605
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
4606
+ drawProjectedEye(context, {
4607
+ x: faceEyeSpacing,
4608
+ y: faceEyeYOffset,
4609
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
4610
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
4611
+ drawProjectedMouth(context, [
4612
+ { x: -mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -mouthHalfWidth, mouthY) },
4613
+ {
4614
+ x: size * morphologyProfile.face.mouthCenterOffsetRatio,
4615
+ y: mouthY +
4616
+ size * morphologyProfile.face.mouthCurveDepthRatio * 0.38 +
4617
+ Math.sin(timeMs / 760 + animationPhase) * size * 0.01 +
4618
+ interaction.gazeY * size * 0.01,
4619
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, size * morphologyProfile.face.mouthCenterOffsetRatio, mouthY),
4620
+ },
4621
+ { x: mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY) },
4622
+ ], mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, palette, size);
4623
+ },
4624
+ };
4625
+ /**
4626
+ * Draws the atmospheric underwater glow behind the octopus mesh.
4627
+ *
4628
+ * @private helper of `octopus3dAvatarVisual`
4629
+ */
4630
+ function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
4631
+ const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.1, sceneCenterY - size * 0.2 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY - size * 0.04, size * 0.62);
4632
+ glowGradient.addColorStop(0, `${palette.highlight}5c`);
4633
+ glowGradient.addColorStop(0.36, `${palette.accent}24`);
4634
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
4635
+ context.fillStyle = glowGradient;
4636
+ context.fillRect(0, 0, size, size);
4637
+ const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1700) * size * 0.05, sceneCenterY + size * 0.23, size * 0.08, sceneCenterX, sceneCenterY + size * 0.28, size * 0.5);
4638
+ lowerGradient.addColorStop(0, `${palette.secondary}1d`);
4639
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
4640
+ context.fillStyle = lowerGradient;
4641
+ context.fillRect(0, 0, size, size);
4642
+ }
4643
+ /**
4644
+ * Draws the soft ground shadow below the octopus.
4645
+ *
4646
+ * @private helper of `octopus3dAvatarVisual`
4647
+ */
4648
+ function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
4649
+ context.save();
4650
+ context.fillStyle = `${palette.shadow}66`;
4651
+ context.filter = `blur(${size * 0.022}px)`;
4652
+ context.beginPath();
4653
+ context.ellipse(size * 0.5 + interaction.gazeX * size * 0.04, size * 0.87 + Math.sin(timeMs / 920) * size * 0.008, size * (0.18 + interaction.intensity * 0.02), size * 0.06, 0, 0, Math.PI * 2);
4654
+ context.fill();
4655
+ context.restore();
4656
+ }
4657
+ /**
4658
+ * Resolves visible projected patches for one rotated ellipsoid mesh.
4659
+ *
4660
+ * @private helper of `octopus3dAvatarVisual`
4661
+ */
4662
+ function resolveVisibleEllipsoidPatches(options) {
4663
+ const { center, radiusX, radiusY, radiusZ, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, verticalColorBias, outlineColor, } = options;
4664
+ const latitudePatchCount = 10;
4665
+ const longitudePatchCount = 18;
4666
+ const surfacePatches = [];
4667
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
4668
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
4669
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
4670
+ const verticalProgress = (latitudeIndex + 0.5) / latitudePatchCount;
4671
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
4672
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
4673
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
4674
+ const localCorners = [
4675
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, startLatitude, startLongitude),
4676
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, startLatitude, endLongitude),
4677
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, endLatitude, endLongitude),
4678
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, endLatitude, startLongitude),
4679
+ ];
4680
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
4681
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
4682
+ if (surfaceNormal.z <= 0.01) {
4683
+ continue;
4684
+ }
4685
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
4686
+ surfacePatches.push({
4687
+ corners: projectedCorners,
4688
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
4689
+ transformedCorners.length,
4690
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
4691
+ fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
4692
+ outlineColor,
4693
+ });
4694
+ }
4695
+ }
4696
+ return surfacePatches;
4697
+ }
4698
+ /**
4699
+ * Samples one point on an ellipsoid aligned to the local axes.
4700
+ *
4701
+ * @private helper of `octopus3dAvatarVisual`
4702
+ */
4703
+ function sampleEllipsoidPoint(radiusX, radiusY, radiusZ, latitude, longitude) {
4704
+ const cosineLatitude = Math.cos(latitude);
4705
+ return {
4706
+ x: Math.sin(longitude) * cosineLatitude * radiusX,
4707
+ y: Math.sin(latitude) * radiusY,
4708
+ z: Math.cos(longitude) * cosineLatitude * radiusZ,
4709
+ };
4710
+ }
4711
+ /**
4712
+ * Resolves one base fill tone for a surface patch across the octopus body.
4713
+ *
4714
+ * @private helper of `octopus3dAvatarVisual`
4715
+ */
4716
+ function resolveSurfacePatchFillStyle(palette, verticalProgress) {
4717
+ const clampedVerticalProgress = clampNumber$1(verticalProgress, 0, 1);
4718
+ if (clampedVerticalProgress < 0.2) {
4719
+ return palette.highlight;
4720
+ }
4721
+ if (clampedVerticalProgress < 0.45) {
4722
+ return palette.secondary;
4723
+ }
4724
+ if (clampedVerticalProgress < 0.8) {
4725
+ return palette.primary;
4726
+ }
4727
+ return `${palette.shadow}f2`;
4728
+ }
4729
+ /**
4730
+ * Draws one projected mesh patch with organic shading.
4731
+ *
4732
+ * @private helper of `octopus3dAvatarVisual`
4733
+ */
4734
+ function drawSurfacePatch(context, surfacePatch) {
4735
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
4736
+ if (surfacePatch.lightIntensity > 0) {
4737
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.15 * surfacePatch.lightIntensity})`);
4738
+ }
4739
+ else if (surfacePatch.lightIntensity < 0) {
4740
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
4741
+ }
4742
+ context.save();
4743
+ context.beginPath();
4744
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
4745
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
4746
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
4747
+ }
4748
+ context.closePath();
4749
+ context.strokeStyle = surfacePatch.outlineColor;
4750
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0044);
4751
+ context.lineJoin = 'round';
4752
+ context.stroke();
4753
+ context.restore();
4754
+ }
4755
+ /**
4756
+ * Creates the projected 3D tentacle strokes orbiting around the lower octopus body.
4757
+ *
4758
+ * @private helper of `octopus3dAvatarVisual`
4759
+ */
4760
+ function createOctopusTentacleStrokes(options) {
4761
+ const { createRandom, morphologyProfile, timeMs, size, center, radiusX, radiusY, radiusZ, rotationX, rotationY, sceneCenterX, sceneCenterY, animationPhase, } = options;
4762
+ return Array.from({ length: morphologyProfile.tentacles.count }, (_, tentacleIndex) => {
4763
+ const tentacleRandom = createRandom(`octopus3d-tentacle-${tentacleIndex}`);
4764
+ const spreadProgress = morphologyProfile.tentacles.count === 1 ? 0.5 : tentacleIndex / (morphologyProfile.tentacles.count - 1);
4765
+ const orbitAngle = -Math.PI * 0.92 + spreadProgress * Math.PI * 1.84 + (tentacleRandom() - 0.5) * 0.16;
4766
+ const flowLength = size * (0.19 + morphologyProfile.tentacles.flowLengthScale * 0.075 + tentacleRandom() * 0.018);
4767
+ const lateralReach = size *
4768
+ (0.08 + morphologyProfile.tentacles.lateralReachScale * 0.05 + Math.abs(Math.sin(orbitAngle)) * 0.018);
4769
+ const depthReach = size * (0.028 + morphologyProfile.tentacles.tipReachScale * 0.032);
4770
+ const sway = Math.sin(timeMs / (760 + tentacleIndex * 36) + animationPhase + tentacleRandom() * Math.PI * 2);
4771
+ const anchorPoint = {
4772
+ x: Math.sin(orbitAngle) * radiusX * (0.84 + tentacleRandom() * 0.08),
4773
+ y: radiusY * (0.22 + tentacleRandom() * 0.18),
4774
+ z: Math.cos(orbitAngle) * radiusZ * (0.72 + tentacleRandom() * 0.12),
4775
+ };
4776
+ const controlPointOne = {
4777
+ x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * 0.44,
4778
+ y: anchorPoint.y + flowLength * 0.26,
4779
+ z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.3 + sway * size * 0.012,
4780
+ };
4781
+ const controlPointTwo = {
4782
+ x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
4783
+ y: anchorPoint.y + flowLength * 0.66,
4784
+ z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.72 + sway * size * 0.02,
4785
+ };
4786
+ const endPoint = {
4787
+ x: anchorPoint.x +
4788
+ Math.sin(orbitAngle) * lateralReach * (1.02 + morphologyProfile.tentacles.tipWidthScale * 0.12) +
4789
+ sway * size * 0.028,
4790
+ y: anchorPoint.y + flowLength,
4791
+ z: anchorPoint.z + Math.cos(orbitAngle) * depthReach + sway * size * 0.016,
4792
+ };
4793
+ const scenePoints = Array.from({ length: 12 }, (_, sampleIndex) => transformScenePoint(sampleCubicBezierPoint3D(anchorPoint, controlPointOne, controlPointTwo, endPoint, sampleIndex / 11), center, rotationX, rotationY));
4794
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
4795
+ const averageDepth = scenePoints.reduce((depthSum, scenePoint) => depthSum + scenePoint.z, 0) / scenePoints.length;
4796
+ return {
4797
+ projectedPoints,
4798
+ averageDepth,
4799
+ isFrontFacing: averageDepth >= center.z - size * 0.006,
4800
+ baseWidth: size *
4801
+ (0.019 +
4802
+ morphologyProfile.tentacles.baseWidthScale * 0.007 +
4803
+ tentacleRandom() * 0.003 +
4804
+ Math.abs(Math.sin(orbitAngle)) * 0.002),
4805
+ tipWidth: size * (0.0046 + morphologyProfile.tentacles.tipWidthScale * 0.0018),
4806
+ colorBias: tentacleRandom(),
4807
+ };
4808
+ });
4809
+ }
4810
+ /**
4811
+ * Samples one point on a cubic Bezier curve in 3D.
4812
+ *
4813
+ * @private helper of `octopus3dAvatarVisual`
4814
+ */
4815
+ function sampleCubicBezierPoint3D(startPoint, controlPointOne, controlPointTwo, endPoint, progress) {
4816
+ const inverseProgress = 1 - progress;
4817
+ return {
4818
+ x: inverseProgress * inverseProgress * inverseProgress * startPoint.x +
4819
+ 3 * inverseProgress * inverseProgress * progress * controlPointOne.x +
4820
+ 3 * inverseProgress * progress * progress * controlPointTwo.x +
4821
+ progress * progress * progress * endPoint.x,
4822
+ y: inverseProgress * inverseProgress * inverseProgress * startPoint.y +
4823
+ 3 * inverseProgress * inverseProgress * progress * controlPointOne.y +
4824
+ 3 * inverseProgress * progress * progress * controlPointTwo.y +
4825
+ progress * progress * progress * endPoint.y,
4826
+ z: inverseProgress * inverseProgress * inverseProgress * startPoint.z +
4827
+ 3 * inverseProgress * inverseProgress * progress * controlPointOne.z +
4828
+ 3 * inverseProgress * progress * progress * controlPointTwo.z +
4829
+ progress * progress * progress * endPoint.z,
4830
+ };
4831
+ }
4832
+ /**
4833
+ * Draws one projected tentacle stroke with a slim highlight ridge.
4834
+ *
4835
+ * @private helper of `octopus3dAvatarVisual`
4836
+ */
4837
+ function drawTentacleStroke(context, tentacleStroke, palette) {
4838
+ const projectedSegments = tentacleStroke.projectedPoints.length - 1;
4839
+ for (let segmentIndex = 0; segmentIndex < projectedSegments; segmentIndex++) {
4840
+ const startPoint = tentacleStroke.projectedPoints[segmentIndex];
4841
+ const endPoint = tentacleStroke.projectedPoints[segmentIndex + 1];
4842
+ const progress = segmentIndex / projectedSegments;
4843
+ const width = tentacleStroke.baseWidth + (tentacleStroke.tipWidth - tentacleStroke.baseWidth) * progress;
4844
+ context.beginPath();
4845
+ context.moveTo(startPoint.x, startPoint.y);
4846
+ context.lineTo(endPoint.x, endPoint.y);
4847
+ context.strokeStyle =
4848
+ tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
4849
+ context.lineWidth = width;
4850
+ context.lineCap = 'round';
4851
+ context.stroke();
4852
+ context.beginPath();
4853
+ context.moveTo(startPoint.x, startPoint.y);
4854
+ context.lineTo(endPoint.x, endPoint.y);
4855
+ context.strokeStyle = tentacleStroke.isFrontFacing ? `${palette.highlight}80` : `${palette.highlight}40`;
4856
+ context.lineWidth = Math.max(1, width * 0.34);
4857
+ context.lineCap = 'round';
4858
+ context.stroke();
4859
+ }
4860
+ }
4861
+ /**
4862
+ * Resolves the front surface depth on an ellipsoid for one local face point.
4863
+ *
4864
+ * @private helper of `octopus3dAvatarVisual`
4865
+ */
4866
+ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
4867
+ const normalizedX = x / radiusX;
4868
+ const normalizedY = y / radiusY;
4869
+ const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
4870
+ return Math.sqrt(remainingDepthRatio) * radiusZ;
4871
+ }
4872
+ /**
4873
+ * Draws one projected eye on the turned octopus mantle.
4874
+ *
4875
+ * @private helper of `octopus3dAvatarVisual`
4876
+ */
4877
+ function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
4878
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
4879
+ if (centerScenePoint.z <= center.z) {
4880
+ return;
4881
+ }
4882
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
4883
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
4884
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
4885
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
4886
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
4887
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
4888
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
4889
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
4890
+ return;
4891
+ }
4892
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
4893
+ radiusX: projectedRadiusX,
4894
+ radiusY: projectedRadiusY,
4895
+ timeMs,
4896
+ phase,
4897
+ interaction,
4898
+ });
4899
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
4900
+ context.save();
4901
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
4902
+ context.rotate(rotation);
4903
+ context.beginPath();
4904
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
4905
+ context.fillStyle = '#f8fbff';
4906
+ context.fill();
4907
+ context.clip();
4908
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
4909
+ irisGradient.addColorStop(0, palette.highlight);
4910
+ irisGradient.addColorStop(0.56, palette.secondary);
4911
+ irisGradient.addColorStop(1, palette.shadow);
4912
+ context.beginPath();
4913
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
4914
+ context.fillStyle = irisGradient;
4915
+ context.fill();
4916
+ context.beginPath();
4917
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
4918
+ context.fillStyle = palette.ink;
4919
+ context.fill();
4920
+ context.beginPath();
4921
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
4922
+ context.fillStyle = '#ffffff';
4923
+ context.fill();
4924
+ context.restore();
4925
+ context.save();
4926
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
4927
+ context.rotate(rotation);
4928
+ context.beginPath();
4929
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
4930
+ context.strokeStyle = `${palette.shadow}cc`;
4931
+ context.lineWidth = projectedRadiusX * 0.16;
4932
+ context.stroke();
4933
+ context.beginPath();
4934
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
4935
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
4936
+ context.strokeStyle = `${palette.shadow}73`;
4937
+ context.lineWidth = projectedRadiusX * 0.14;
4938
+ context.lineCap = 'round';
4939
+ context.stroke();
4940
+ if (eyeStyle.lowerLidOpacity > 0) {
4941
+ context.beginPath();
4942
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
4943
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
4944
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
4945
+ context.lineWidth = projectedRadiusX * 0.08;
4946
+ context.lineCap = 'round';
4947
+ context.stroke();
4948
+ }
4949
+ context.restore();
4950
+ }
4951
+ /**
4952
+ * Draws a subtle projected mouth arc across the front of the mantle.
4953
+ *
4954
+ * @private helper of `octopus3dAvatarVisual`
4955
+ */
4956
+ function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
4957
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
4958
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
4959
+ return;
4960
+ }
4961
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
4962
+ context.beginPath();
4963
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
4964
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
4965
+ context.strokeStyle = `${palette.ink}b8`;
4966
+ context.lineWidth = Math.max(1.1, size * 0.009);
4967
+ context.lineCap = 'round';
4968
+ context.stroke();
4969
+ }
4970
+ /**
4971
+ * Draws one filled projected quad.
4972
+ *
4973
+ * @private helper of `octopus3dAvatarVisual`
4974
+ */
4975
+ function drawProjectedQuad(context, corners, fillStyle) {
4976
+ context.beginPath();
4977
+ context.moveTo(corners[0].x, corners[0].y);
4978
+ context.lineTo(corners[1].x, corners[1].y);
4979
+ context.lineTo(corners[2].x, corners[2].y);
4980
+ context.lineTo(corners[3].x, corners[3].y);
4981
+ context.closePath();
4982
+ context.fillStyle = fillStyle;
4983
+ context.fill();
4984
+ }
4985
+ /**
4986
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
4987
+ *
4988
+ * @private helper of `octopus3dAvatarVisual`
4989
+ */
4990
+ function formatAlphaHex(opacity) {
4991
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
4992
+ .toString(16)
4993
+ .padStart(2, '0');
4994
+ }
4995
+
4481
4996
  /* eslint-disable no-magic-numbers */
4482
4997
  /**
4483
4998
  * Octopus avatar visual.
@@ -5246,6 +5761,7 @@ const AVATAR_VISUALS = [
5246
5761
  octopusAvatarVisual,
5247
5762
  octopus2AvatarVisual,
5248
5763
  octopus3AvatarVisual,
5764
+ octopus3dAvatarVisual,
5249
5765
  asciiOctopusAvatarVisual,
5250
5766
  minecraftAvatarVisual,
5251
5767
  minecraft2AvatarVisual,
@@ -11838,16 +12354,22 @@ function resolveTeamTeammateLabels(teamContent, teammates) {
11838
12354
  * Each teammate is listed with its tool name, TEAM instructions, and optional profile hints.
11839
12355
  */
11840
12356
  function buildTeamSystemMessageBody(teamEntries) {
11841
- const lines = [
11842
- ...TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES,
11843
- '',
11844
- ...teamEntries.map((entry, index) => {
11845
- const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
11846
- const detailLines = collectTeamEntryDetails(entry).map(formatTeamEntryDetailLine);
11847
- return [toolLine, ...detailLines].join('\n');
11848
- }),
11849
- ];
11850
- return lines.join('\n');
12357
+ const teammateSections = teamEntries.map((entry, index) => {
12358
+ const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
12359
+ const detailLines = collectTeamEntryDetails(entry).map(formatTeamEntryDetailLine);
12360
+ if (detailLines.length === 0) {
12361
+ return toolLine;
12362
+ }
12363
+ return spaceTrim$1((block) => `
12364
+ ${toolLine}
12365
+ ${block(detailLines.join('\n'))}
12366
+ `);
12367
+ });
12368
+ return spaceTrim$1((block) => `
12369
+ ${block(TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES.join('\n'))}
12370
+
12371
+ ${block(teammateSections.join('\n\n'))}
12372
+ `);
11851
12373
  }
11852
12374
  /**
11853
12375
  * Builds the model-visible description for one teammate tool.
@@ -40239,11 +40761,11 @@ function capitalize(word) {
40239
40761
  */
40240
40762
  function pipelineJsonToString(pipelineJson) {
40241
40763
  const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
40242
- let pipelineString = `# ${title}`;
40243
- if (description) {
40244
- pipelineString += '\n\n';
40245
- pipelineString += description;
40246
- }
40764
+ let pipelineString = spaceTrim$1((block) => `
40765
+ # ${title}
40766
+
40767
+ ${block(description || '')}
40768
+ `);
40247
40769
  const commands = [];
40248
40770
  if (pipelineUrl) {
40249
40771
  commands.push(`PIPELINE URL ${pipelineUrl}`);
@@ -40259,20 +40781,17 @@ function pipelineJsonToString(pipelineJson) {
40259
40781
  for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
40260
40782
  commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
40261
40783
  }
40262
- pipelineString += '\n\n';
40263
- pipelineString += commands.map((command) => `- ${command}`).join('\n');
40784
+ pipelineString = spaceTrim$1((block) => `
40785
+ ${block(pipelineString)}
40786
+
40787
+ ${block(commands.map((command) => `- ${command}`).join('\n'))}
40788
+ `);
40264
40789
  for (const task of tasks) {
40265
40790
  const {
40266
40791
  /* Note: Not using:> name, */
40267
40792
  title, description,
40268
40793
  /* Note: dependentParameterNames, */
40269
40794
  jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
40270
- pipelineString += '\n\n';
40271
- pipelineString += `## ${title}`;
40272
- if (description) {
40273
- pipelineString += '\n\n';
40274
- pipelineString += description;
40275
- }
40276
40795
  const commands = [];
40277
40796
  let contentLanguage = 'text';
40278
40797
  if (taskType === 'PROMPT_TASK') {
@@ -40335,18 +40854,23 @@ function pipelineJsonToString(pipelineJson) {
40335
40854
  commands.push(`FORMAT JSON`);
40336
40855
  }
40337
40856
  } /* not else */
40338
- pipelineString += '\n\n';
40339
- pipelineString += commands.map((command) => `- ${command}`).join('\n');
40340
- pipelineString += '\n\n';
40341
- pipelineString += '```' + contentLanguage;
40342
- pipelineString += '\n';
40343
- pipelineString += spaceTrim$1(content);
40344
- // <- TODO: [main] !!3 Escape
40345
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
40346
- pipelineString += '\n';
40347
- pipelineString += '```';
40348
- pipelineString += '\n\n';
40349
- pipelineString += `\`-> {${resultingParameterName}}\``; // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
40857
+ pipelineString = spaceTrim$1((block) => `
40858
+ ${block(pipelineString)}
40859
+
40860
+ ## ${title}
40861
+
40862
+ ${block(description || '')}
40863
+
40864
+ ${block(commands.map((command) => `- ${command}`).join('\n'))}
40865
+
40866
+ \`\`\`${contentLanguage}
40867
+ ${block(spaceTrim$1(content))}
40868
+ \`\`\`
40869
+
40870
+ \`-> {${resultingParameterName}}\`
40871
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
40872
+ // <- TODO: [main] !!3 Escape
40873
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
40350
40874
  }
40351
40875
  return validatePipelineString(pipelineString);
40352
40876
  }
@@ -45415,7 +45939,11 @@ function createExampleInteractionsContent(parseResult, samples) {
45415
45939
  if (examples.length === 0) {
45416
45940
  return null;
45417
45941
  }
45418
- return `## Sample of communication with the agent:\n\n${examples.join('\n\n')}`;
45942
+ return spaceTrim$1((block) => `
45943
+ ## Sample of communication with the agent:
45944
+
45945
+ ${block(examples.join('\n\n'))}
45946
+ `);
45419
45947
  }
45420
45948
  /**
45421
45949
  * Collects the individual lines used in the example interaction section.
@@ -45452,7 +45980,11 @@ function collectExampleInteractionLines(parseResult, samples) {
45452
45980
  function appendSystemMessageSection(requirements, section) {
45453
45981
  return {
45454
45982
  ...requirements,
45455
- systemMessage: requirements.systemMessage + '\n\n' + section,
45983
+ systemMessage: spaceTrim$1((block) => `
45984
+ ${block(requirements.systemMessage)}
45985
+
45986
+ ${block(section)}
45987
+ `),
45456
45988
  };
45457
45989
  }
45458
45990
  /**