@promptbook/components 0.112.0-69 → 0.112.0-71

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/umd/index.umd.js CHANGED
@@ -30,7 +30,7 @@
30
30
  * @generated
31
31
  * @see https://github.com/webgptorg/promptbook
32
32
  */
33
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-69';
33
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-71';
34
34
  /**
35
35
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
36
36
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2844,6 +2844,184 @@
2844
2844
  context.restore();
2845
2845
  }
2846
2846
 
2847
+ /* eslint-disable no-magic-numbers */
2848
+ /**
2849
+ * Default camera distance ratio shared by the proper-3D avatar visuals.
2850
+ *
2851
+ * @private helper of the 3D avatar visuals
2852
+ */
2853
+ const DEFAULT_3D_CAMERA_DISTANCE_RATIO = 1.4;
2854
+ /**
2855
+ * Clamps one number into the provided range.
2856
+ *
2857
+ * @param value Input value.
2858
+ * @param minimumValue Inclusive lower bound.
2859
+ * @param maximumValue Inclusive upper bound.
2860
+ * @returns Clamped value.
2861
+ *
2862
+ * @private helper of the 3D avatar visuals
2863
+ */
2864
+ function clampNumber$1(value, minimumValue, maximumValue) {
2865
+ return Math.min(maximumValue, Math.max(minimumValue, value));
2866
+ }
2867
+ /**
2868
+ * Rotates one point around the local Y axis.
2869
+ *
2870
+ * @param point Source point.
2871
+ * @param angle Rotation angle in radians.
2872
+ * @returns Rotated point.
2873
+ *
2874
+ * @private helper of the 3D avatar visuals
2875
+ */
2876
+ function rotatePointAroundY(point, angle) {
2877
+ const cosine = Math.cos(angle);
2878
+ const sine = Math.sin(angle);
2879
+ return {
2880
+ x: point.x * cosine + point.z * sine,
2881
+ y: point.y,
2882
+ z: -point.x * sine + point.z * cosine,
2883
+ };
2884
+ }
2885
+ /**
2886
+ * Rotates one point around the local X axis.
2887
+ *
2888
+ * @param point Source point.
2889
+ * @param angle Rotation angle in radians.
2890
+ * @returns Rotated point.
2891
+ *
2892
+ * @private helper of the 3D avatar visuals
2893
+ */
2894
+ function rotatePointAroundX(point, angle) {
2895
+ const cosine = Math.cos(angle);
2896
+ const sine = Math.sin(angle);
2897
+ return {
2898
+ x: point.x,
2899
+ y: point.y * cosine - point.z * sine,
2900
+ z: point.y * sine + point.z * cosine,
2901
+ };
2902
+ }
2903
+ /**
2904
+ * Applies the local rotations and translation to one scene point.
2905
+ *
2906
+ * @param localPoint Point in local object space.
2907
+ * @param center Object center in scene space.
2908
+ * @param rotationX Object pitch in radians.
2909
+ * @param rotationY Object yaw in radians.
2910
+ * @returns Transformed scene-space point.
2911
+ *
2912
+ * @private helper of the 3D avatar visuals
2913
+ */
2914
+ function transformScenePoint(localPoint, center, rotationX, rotationY) {
2915
+ const yawedPoint = rotatePointAroundY(localPoint, rotationY);
2916
+ const pitchedPoint = rotatePointAroundX(yawedPoint, rotationX);
2917
+ return {
2918
+ x: center.x + pitchedPoint.x,
2919
+ y: center.y + pitchedPoint.y,
2920
+ z: center.z + pitchedPoint.z,
2921
+ };
2922
+ }
2923
+ /**
2924
+ * Projects one scene point into canvas coordinates.
2925
+ *
2926
+ * @param point Scene-space point.
2927
+ * @param size Canvas size in CSS pixels.
2928
+ * @param sceneCenterX Horizontal scene center.
2929
+ * @param sceneCenterY Vertical scene center.
2930
+ * @param cameraDistanceRatio Optional camera distance ratio.
2931
+ * @returns Projected point.
2932
+ *
2933
+ * @private helper of the 3D avatar visuals
2934
+ */
2935
+ function projectScenePoint(point, size, sceneCenterX, sceneCenterY, cameraDistanceRatio = DEFAULT_3D_CAMERA_DISTANCE_RATIO) {
2936
+ const cameraDistance = size * cameraDistanceRatio;
2937
+ const perspectiveScale = cameraDistance / Math.max(cameraDistance - point.z, cameraDistance * 0.35);
2938
+ return {
2939
+ x: sceneCenterX + point.x * perspectiveScale,
2940
+ y: sceneCenterY + point.y * perspectiveScale,
2941
+ z: point.z,
2942
+ };
2943
+ }
2944
+ /**
2945
+ * Subtracts one 3D point from another.
2946
+ *
2947
+ * @param leftPoint Left point.
2948
+ * @param rightPoint Right point.
2949
+ * @returns Difference vector.
2950
+ *
2951
+ * @private helper of the 3D avatar visuals
2952
+ */
2953
+ function subtractPoint3D(leftPoint, rightPoint) {
2954
+ return {
2955
+ x: leftPoint.x - rightPoint.x,
2956
+ y: leftPoint.y - rightPoint.y,
2957
+ z: leftPoint.z - rightPoint.z,
2958
+ };
2959
+ }
2960
+ /**
2961
+ * Computes the 3D cross product of two vectors.
2962
+ *
2963
+ * @param leftVector Left vector.
2964
+ * @param rightVector Right vector.
2965
+ * @returns Cross product.
2966
+ *
2967
+ * @private helper of the 3D avatar visuals
2968
+ */
2969
+ function crossProduct3D(leftVector, rightVector) {
2970
+ return {
2971
+ x: leftVector.y * rightVector.z - leftVector.z * rightVector.y,
2972
+ y: leftVector.z * rightVector.x - leftVector.x * rightVector.z,
2973
+ z: leftVector.x * rightVector.y - leftVector.y * rightVector.x,
2974
+ };
2975
+ }
2976
+ /**
2977
+ * Computes the 3D dot product of two vectors.
2978
+ *
2979
+ * @param leftVector Left vector.
2980
+ * @param rightVector Right vector.
2981
+ * @returns Dot product.
2982
+ *
2983
+ * @private helper of the 3D avatar visuals
2984
+ */
2985
+ function dotProduct3D(leftVector, rightVector) {
2986
+ return leftVector.x * rightVector.x + leftVector.y * rightVector.y + leftVector.z * rightVector.z;
2987
+ }
2988
+ /**
2989
+ * Normalizes one 3D vector while keeping zero vectors stable.
2990
+ *
2991
+ * @param vector Source vector.
2992
+ * @returns Normalized vector.
2993
+ *
2994
+ * @private helper of the 3D avatar visuals
2995
+ */
2996
+ function normalizeVector3(vector) {
2997
+ const length = Math.hypot(vector.x, vector.y, vector.z);
2998
+ if (length === 0) {
2999
+ return vector;
3000
+ }
3001
+ return {
3002
+ x: vector.x / length,
3003
+ y: vector.y / length,
3004
+ z: vector.z / length,
3005
+ };
3006
+ }
3007
+ /**
3008
+ * Measures the perimeter of one projected quad.
3009
+ *
3010
+ * @param corners Quad corners.
3011
+ * @returns Perimeter length.
3012
+ *
3013
+ * @private helper of the 3D avatar visuals
3014
+ */
3015
+ function getProjectedQuadPerimeter(corners) {
3016
+ let perimeter = 0;
3017
+ for (let cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
3018
+ const currentCorner = corners[cornerIndex];
3019
+ const nextCorner = corners[(cornerIndex + 1) % corners.length];
3020
+ perimeter += Math.hypot(nextCorner.x - currentCorner.x, nextCorner.y - currentCorner.y);
3021
+ }
3022
+ return perimeter;
3023
+ }
3024
+
2847
3025
  /* eslint-disable no-magic-numbers */
2848
3026
  /**
2849
3027
  * Builds the seeded six-face texture pack used by the Minecraft-style head cuboid.
@@ -3016,18 +3194,12 @@
3016
3194
  }
3017
3195
 
3018
3196
  /* eslint-disable no-magic-numbers */
3019
- /**
3020
- * Fixed scene camera distance used for the proper-3D projection.
3021
- *
3022
- * @private helper of `minecraft2AvatarVisual`
3023
- */
3024
- const CAMERA_DISTANCE_RATIO = 1.4;
3025
3197
  /**
3026
3198
  * Shared light direction used to shade projected cuboid faces.
3027
3199
  *
3028
3200
  * @private helper of `minecraft2AvatarVisual`
3029
3201
  */
3030
- const LIGHT_DIRECTION = normalizeVector3({
3202
+ const LIGHT_DIRECTION$1 = normalizeVector3({
3031
3203
  x: 0.4,
3032
3204
  y: -0.65,
3033
3205
  z: 0.92,
@@ -3239,7 +3411,7 @@
3239
3411
  corners: projectedCorners,
3240
3412
  texture: faceDefinition.texture,
3241
3413
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
3242
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION), -1, 1),
3414
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
3243
3415
  outlineColor: cuboid.outlineColor,
3244
3416
  };
3245
3417
  });
@@ -3266,7 +3438,7 @@
3266
3438
  const endX = (columnIndex + 1) / columns;
3267
3439
  const startY = rowIndex / rows;
3268
3440
  const endY = (rowIndex + 1) / rows;
3269
- drawProjectedQuad(context, [
3441
+ drawProjectedQuad$1(context, [
3270
3442
  interpolateProjectedQuad(face.corners, startX, startY),
3271
3443
  interpolateProjectedQuad(face.corners, endX, startY),
3272
3444
  interpolateProjectedQuad(face.corners, endX, endY),
@@ -3275,10 +3447,10 @@
3275
3447
  }
3276
3448
  }
3277
3449
  if (face.lightIntensity > 0) {
3278
- drawProjectedQuad(context, face.corners, `rgba(255, 255, 255, ${0.15 * face.lightIntensity})`);
3450
+ drawProjectedQuad$1(context, face.corners, `rgba(255, 255, 255, ${0.15 * face.lightIntensity})`);
3279
3451
  }
3280
3452
  else if (face.lightIntensity < 0) {
3281
- drawProjectedQuad(context, face.corners, `rgba(0, 0, 0, ${0.22 * Math.abs(face.lightIntensity)})`);
3453
+ drawProjectedQuad$1(context, face.corners, `rgba(0, 0, 0, ${0.22 * Math.abs(face.lightIntensity)})`);
3282
3454
  }
3283
3455
  context.save();
3284
3456
  context.beginPath();
@@ -3302,7 +3474,7 @@
3302
3474
  *
3303
3475
  * @private helper of `minecraft2AvatarVisual`
3304
3476
  */
3305
- function drawProjectedQuad(context, corners, fillStyle) {
3477
+ function drawProjectedQuad$1(context, corners, fillStyle) {
3306
3478
  context.beginPath();
3307
3479
  context.moveTo(corners[0].x, corners[0].y);
3308
3480
  context.lineTo(corners[1].x, corners[1].y);
@@ -3344,175 +3516,6 @@
3344
3516
  z: startPoint.z + (endPoint.z - startPoint.z) * ratio,
3345
3517
  };
3346
3518
  }
3347
- /**
3348
- * Projects one rotated scene point into canvas coordinates.
3349
- *
3350
- * @param point Scene point.
3351
- * @param size Canvas size in CSS pixels.
3352
- * @param sceneCenterX Horizontal scene center.
3353
- * @param sceneCenterY Vertical scene center.
3354
- * @returns Projected point.
3355
- *
3356
- * @private helper of `minecraft2AvatarVisual`
3357
- */
3358
- function projectScenePoint(point, size, sceneCenterX, sceneCenterY) {
3359
- const cameraDistance = size * CAMERA_DISTANCE_RATIO;
3360
- const perspectiveScale = cameraDistance / Math.max(cameraDistance - point.z, cameraDistance * 0.35);
3361
- return {
3362
- x: sceneCenterX + point.x * perspectiveScale,
3363
- y: sceneCenterY + point.y * perspectiveScale,
3364
- z: point.z,
3365
- };
3366
- }
3367
- /**
3368
- * Applies the local cuboid rotations and translation to one scene point.
3369
- *
3370
- * @param localPoint Point in cuboid-local space.
3371
- * @param center Cuboid center in scene space.
3372
- * @param rotationX Cuboid pitch in radians.
3373
- * @param rotationY Cuboid yaw in radians.
3374
- * @returns Transformed scene-space point.
3375
- *
3376
- * @private helper of `minecraft2AvatarVisual`
3377
- */
3378
- function transformScenePoint(localPoint, center, rotationX, rotationY) {
3379
- const yawedPoint = rotatePointAroundY(localPoint, rotationY);
3380
- const pitchedPoint = rotatePointAroundX(yawedPoint, rotationX);
3381
- return {
3382
- x: center.x + pitchedPoint.x,
3383
- y: center.y + pitchedPoint.y,
3384
- z: center.z + pitchedPoint.z,
3385
- };
3386
- }
3387
- /**
3388
- * Rotates one point around the local Y axis.
3389
- *
3390
- * @param point Source point.
3391
- * @param angle Rotation angle in radians.
3392
- * @returns Rotated point.
3393
- *
3394
- * @private helper of `minecraft2AvatarVisual`
3395
- */
3396
- function rotatePointAroundY(point, angle) {
3397
- const cosine = Math.cos(angle);
3398
- const sine = Math.sin(angle);
3399
- return {
3400
- x: point.x * cosine + point.z * sine,
3401
- y: point.y,
3402
- z: -point.x * sine + point.z * cosine,
3403
- };
3404
- }
3405
- /**
3406
- * Rotates one point around the local X axis.
3407
- *
3408
- * @param point Source point.
3409
- * @param angle Rotation angle in radians.
3410
- * @returns Rotated point.
3411
- *
3412
- * @private helper of `minecraft2AvatarVisual`
3413
- */
3414
- function rotatePointAroundX(point, angle) {
3415
- const cosine = Math.cos(angle);
3416
- const sine = Math.sin(angle);
3417
- return {
3418
- x: point.x,
3419
- y: point.y * cosine - point.z * sine,
3420
- z: point.y * sine + point.z * cosine,
3421
- };
3422
- }
3423
- /**
3424
- * Subtracts one 3D point from another.
3425
- *
3426
- * @param leftPoint Left point.
3427
- * @param rightPoint Right point.
3428
- * @returns Difference vector.
3429
- *
3430
- * @private helper of `minecraft2AvatarVisual`
3431
- */
3432
- function subtractPoint3D(leftPoint, rightPoint) {
3433
- return {
3434
- x: leftPoint.x - rightPoint.x,
3435
- y: leftPoint.y - rightPoint.y,
3436
- z: leftPoint.z - rightPoint.z,
3437
- };
3438
- }
3439
- /**
3440
- * Computes the 3D cross product of two vectors.
3441
- *
3442
- * @param leftVector Left vector.
3443
- * @param rightVector Right vector.
3444
- * @returns Cross product.
3445
- *
3446
- * @private helper of `minecraft2AvatarVisual`
3447
- */
3448
- function crossProduct3D(leftVector, rightVector) {
3449
- return {
3450
- x: leftVector.y * rightVector.z - leftVector.z * rightVector.y,
3451
- y: leftVector.z * rightVector.x - leftVector.x * rightVector.z,
3452
- z: leftVector.x * rightVector.y - leftVector.y * rightVector.x,
3453
- };
3454
- }
3455
- /**
3456
- * Computes the 3D dot product of two vectors.
3457
- *
3458
- * @param leftVector Left vector.
3459
- * @param rightVector Right vector.
3460
- * @returns Dot product.
3461
- *
3462
- * @private helper of `minecraft2AvatarVisual`
3463
- */
3464
- function dotProduct3D(leftVector, rightVector) {
3465
- return leftVector.x * rightVector.x + leftVector.y * rightVector.y + leftVector.z * rightVector.z;
3466
- }
3467
- /**
3468
- * Normalizes one 3D vector while keeping zero vectors stable.
3469
- *
3470
- * @param vector Source vector.
3471
- * @returns Normalized vector.
3472
- *
3473
- * @private helper of `minecraft2AvatarVisual`
3474
- */
3475
- function normalizeVector3(vector) {
3476
- const length = Math.hypot(vector.x, vector.y, vector.z);
3477
- if (length === 0) {
3478
- return vector;
3479
- }
3480
- return {
3481
- x: vector.x / length,
3482
- y: vector.y / length,
3483
- z: vector.z / length,
3484
- };
3485
- }
3486
- /**
3487
- * Clamps one number into the provided range.
3488
- *
3489
- * @param value Input value.
3490
- * @param minimumValue Inclusive lower bound.
3491
- * @param maximumValue Inclusive upper bound.
3492
- * @returns Clamped value.
3493
- *
3494
- * @private helper of `minecraft2AvatarVisual`
3495
- */
3496
- function clampNumber$1(value, minimumValue, maximumValue) {
3497
- return Math.min(maximumValue, Math.max(minimumValue, value));
3498
- }
3499
- /**
3500
- * Measures the perimeter of one projected quad.
3501
- *
3502
- * @param corners Quad corners.
3503
- * @returns Perimeter length.
3504
- *
3505
- * @private helper of `minecraft2AvatarVisual`
3506
- */
3507
- function getProjectedQuadPerimeter(corners) {
3508
- let perimeter = 0;
3509
- for (let cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) {
3510
- const currentCorner = corners[cornerIndex];
3511
- const nextCorner = corners[(cornerIndex + 1) % corners.length];
3512
- perimeter += Math.hypot(nextCorner.x - currentCorner.x, nextCorner.y - currentCorner.y);
3513
- }
3514
- return perimeter;
3515
- }
3516
3519
 
3517
3520
  /* eslint-disable no-magic-numbers */
3518
3521
  /**
@@ -4081,7 +4084,7 @@
4081
4084
  *
4082
4085
  * @private helper of `octopus3AvatarVisual`
4083
4086
  */
4084
- function formatAlphaHex(opacity) {
4087
+ function formatAlphaHex$1(opacity) {
4085
4088
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
4086
4089
  .toString(16)
4087
4090
  .padStart(2, '0');
@@ -4460,7 +4463,7 @@
4460
4463
  context.beginPath();
4461
4464
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
4462
4465
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
4463
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
4466
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
4464
4467
  context.lineWidth = radiusX * 0.08;
4465
4468
  context.lineCap = 'round';
4466
4469
  context.stroke();
@@ -4468,6 +4471,518 @@
4468
4471
  context.restore();
4469
4472
  }
4470
4473
 
4474
+ /* eslint-disable no-magic-numbers */
4475
+ /**
4476
+ * Light direction used by the organic 3D octopus shading.
4477
+ *
4478
+ * @private helper of `octopus3dAvatarVisual`
4479
+ */
4480
+ const LIGHT_DIRECTION = normalizeVector3({
4481
+ x: 0.48,
4482
+ y: -0.62,
4483
+ z: 0.94,
4484
+ });
4485
+ /**
4486
+ * Proper 3D Octopus visual built from projected organic meshes and tentacles.
4487
+ *
4488
+ * @private built-in avatar visual
4489
+ */
4490
+ const octopus3dAvatarVisual = {
4491
+ id: 'octopus3d',
4492
+ title: 'Octopus 3D',
4493
+ description: 'Proper 3D octopus portrait with a turning silhouette, expressive eyes, and depth-sorted tentacles.',
4494
+ isAnimated: true,
4495
+ supportsPointerTracking: true,
4496
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
4497
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
4498
+ const animationRandom = createRandom('octopus3d-animation-profile');
4499
+ const eyeRandom = createRandom('octopus3d-eye-profile');
4500
+ const animationPhase = animationRandom() * Math.PI * 2;
4501
+ const sceneCenterX = size * 0.5;
4502
+ const sceneCenterY = size * 0.56;
4503
+ const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
4504
+ const mantleCenter = {
4505
+ x: interaction.bodyOffsetX * size * 0.042 + size * morphologyProfile.body.centerXJitterRatio * 0.65,
4506
+ y: -size * 0.09 + interaction.bodyOffsetY * size * 0.028 + bob,
4507
+ z: interaction.intensity * size * 0.012,
4508
+ };
4509
+ const underbodyCenter = {
4510
+ x: mantleCenter.x * 0.86,
4511
+ y: mantleCenter.y + size * 0.168,
4512
+ z: mantleCenter.z - size * 0.018,
4513
+ };
4514
+ const mantleRadiusX = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch;
4515
+ const mantleRadiusY = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1;
4516
+ const mantleRadiusZ = size * morphologyProfile.body.bodyRadiusRatio * (0.9 + (morphologyProfile.body.horizontalStretch - 1) * 0.3);
4517
+ const underbodyRadiusX = mantleRadiusX * (0.9 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.08);
4518
+ const underbodyRadiusY = mantleRadiusY * (0.44 + morphologyProfile.body.lowerDropRatio * 3.1);
4519
+ const underbodyRadiusZ = mantleRadiusZ * 0.78;
4520
+ const bodyYaw = -0.18 +
4521
+ Math.sin(timeMs / 2400 + animationPhase) * 0.05 +
4522
+ interaction.bodyOffsetX * 0.18 +
4523
+ interaction.gazeX * 0.22;
4524
+ const bodyPitch = -0.08 +
4525
+ Math.cos(timeMs / 2700 + animationPhase * 0.6) * 0.025 -
4526
+ interaction.bodyOffsetY * 0.08 -
4527
+ interaction.gazeY * 0.08;
4528
+ const headYaw = bodyYaw - 0.04 + interaction.gazeX * 0.56;
4529
+ const headPitch = bodyPitch - 0.02 - interaction.gazeY * 0.32;
4530
+ const mantlePatches = resolveVisibleEllipsoidPatches({
4531
+ center: mantleCenter,
4532
+ radiusX: mantleRadiusX,
4533
+ radiusY: mantleRadiusY,
4534
+ radiusZ: mantleRadiusZ,
4535
+ rotationX: headPitch,
4536
+ rotationY: headYaw,
4537
+ sceneCenterX,
4538
+ sceneCenterY,
4539
+ size,
4540
+ palette,
4541
+ verticalColorBias: 0,
4542
+ outlineColor: `${palette.highlight}7a`,
4543
+ });
4544
+ const underbodyPatches = resolveVisibleEllipsoidPatches({
4545
+ center: underbodyCenter,
4546
+ radiusX: underbodyRadiusX,
4547
+ radiusY: underbodyRadiusY,
4548
+ radiusZ: underbodyRadiusZ,
4549
+ rotationX: bodyPitch,
4550
+ rotationY: bodyYaw,
4551
+ sceneCenterX,
4552
+ sceneCenterY,
4553
+ size,
4554
+ palette,
4555
+ verticalColorBias: 0.18,
4556
+ outlineColor: `${palette.shadow}8f`,
4557
+ });
4558
+ const tentacleStrokes = createOctopusTentacleStrokes({
4559
+ createRandom,
4560
+ morphologyProfile,
4561
+ timeMs,
4562
+ size,
4563
+ center: underbodyCenter,
4564
+ radiusX: underbodyRadiusX,
4565
+ radiusY: underbodyRadiusY,
4566
+ radiusZ: underbodyRadiusZ,
4567
+ rotationX: bodyPitch,
4568
+ rotationY: bodyYaw,
4569
+ sceneCenterX,
4570
+ sceneCenterY,
4571
+ animationPhase,
4572
+ });
4573
+ const faceEyeSpacing = size * morphologyProfile.face.eyeSpacingRatio * 0.92;
4574
+ const faceEyeYOffset = size * morphologyProfile.face.eyeCenterYOffsetRatio - mantleRadiusY * 0.02;
4575
+ const faceEyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.82;
4576
+ const faceEyeRadiusY = faceEyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.96;
4577
+ const mouthHalfWidth = size * morphologyProfile.face.mouthWidthRatio * 0.92;
4578
+ const mouthY = size * morphologyProfile.face.mouthYOffsetRatio + mantleRadiusY * 0.08;
4579
+ drawAvatarFrame(context, size, palette);
4580
+ drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
4581
+ drawOctopus3dShadow(context, size, palette, interaction, timeMs);
4582
+ for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => !candidateTentacleStroke.isFrontFacing)) {
4583
+ drawTentacleStroke(context, tentacleStroke, palette);
4584
+ }
4585
+ for (const surfacePatch of [...mantlePatches, ...underbodyPatches].sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
4586
+ drawSurfacePatch(context, surfacePatch);
4587
+ }
4588
+ for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
4589
+ drawTentacleStroke(context, tentacleStroke, palette);
4590
+ }
4591
+ drawProjectedEye(context, {
4592
+ x: -faceEyeSpacing,
4593
+ y: faceEyeYOffset,
4594
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
4595
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
4596
+ drawProjectedEye(context, {
4597
+ x: faceEyeSpacing,
4598
+ y: faceEyeYOffset,
4599
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
4600
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
4601
+ drawProjectedMouth(context, [
4602
+ { x: -mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -mouthHalfWidth, mouthY) },
4603
+ {
4604
+ x: size * morphologyProfile.face.mouthCenterOffsetRatio,
4605
+ y: mouthY +
4606
+ size * morphologyProfile.face.mouthCurveDepthRatio * 0.38 +
4607
+ Math.sin(timeMs / 760 + animationPhase) * size * 0.01 +
4608
+ interaction.gazeY * size * 0.01,
4609
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, size * morphologyProfile.face.mouthCenterOffsetRatio, mouthY),
4610
+ },
4611
+ { x: mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY) },
4612
+ ], mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, palette, size);
4613
+ },
4614
+ };
4615
+ /**
4616
+ * Draws the atmospheric underwater glow behind the octopus mesh.
4617
+ *
4618
+ * @private helper of `octopus3dAvatarVisual`
4619
+ */
4620
+ function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
4621
+ 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);
4622
+ glowGradient.addColorStop(0, `${palette.highlight}5c`);
4623
+ glowGradient.addColorStop(0.36, `${palette.accent}24`);
4624
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
4625
+ context.fillStyle = glowGradient;
4626
+ context.fillRect(0, 0, size, size);
4627
+ 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);
4628
+ lowerGradient.addColorStop(0, `${palette.secondary}1d`);
4629
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
4630
+ context.fillStyle = lowerGradient;
4631
+ context.fillRect(0, 0, size, size);
4632
+ }
4633
+ /**
4634
+ * Draws the soft ground shadow below the octopus.
4635
+ *
4636
+ * @private helper of `octopus3dAvatarVisual`
4637
+ */
4638
+ function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
4639
+ context.save();
4640
+ context.fillStyle = `${palette.shadow}66`;
4641
+ context.filter = `blur(${size * 0.022}px)`;
4642
+ context.beginPath();
4643
+ 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);
4644
+ context.fill();
4645
+ context.restore();
4646
+ }
4647
+ /**
4648
+ * Resolves visible projected patches for one rotated ellipsoid mesh.
4649
+ *
4650
+ * @private helper of `octopus3dAvatarVisual`
4651
+ */
4652
+ function resolveVisibleEllipsoidPatches(options) {
4653
+ const { center, radiusX, radiusY, radiusZ, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, verticalColorBias, outlineColor, } = options;
4654
+ const latitudePatchCount = 10;
4655
+ const longitudePatchCount = 18;
4656
+ const surfacePatches = [];
4657
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
4658
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
4659
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
4660
+ const verticalProgress = (latitudeIndex + 0.5) / latitudePatchCount;
4661
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
4662
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
4663
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
4664
+ const localCorners = [
4665
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, startLatitude, startLongitude),
4666
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, startLatitude, endLongitude),
4667
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, endLatitude, endLongitude),
4668
+ sampleEllipsoidPoint(radiusX, radiusY, radiusZ, endLatitude, startLongitude),
4669
+ ];
4670
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
4671
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
4672
+ if (surfaceNormal.z <= 0.01) {
4673
+ continue;
4674
+ }
4675
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
4676
+ surfacePatches.push({
4677
+ corners: projectedCorners,
4678
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
4679
+ transformedCorners.length,
4680
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
4681
+ fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
4682
+ outlineColor,
4683
+ });
4684
+ }
4685
+ }
4686
+ return surfacePatches;
4687
+ }
4688
+ /**
4689
+ * Samples one point on an ellipsoid aligned to the local axes.
4690
+ *
4691
+ * @private helper of `octopus3dAvatarVisual`
4692
+ */
4693
+ function sampleEllipsoidPoint(radiusX, radiusY, radiusZ, latitude, longitude) {
4694
+ const cosineLatitude = Math.cos(latitude);
4695
+ return {
4696
+ x: Math.sin(longitude) * cosineLatitude * radiusX,
4697
+ y: Math.sin(latitude) * radiusY,
4698
+ z: Math.cos(longitude) * cosineLatitude * radiusZ,
4699
+ };
4700
+ }
4701
+ /**
4702
+ * Resolves one base fill tone for a surface patch across the octopus body.
4703
+ *
4704
+ * @private helper of `octopus3dAvatarVisual`
4705
+ */
4706
+ function resolveSurfacePatchFillStyle(palette, verticalProgress) {
4707
+ const clampedVerticalProgress = clampNumber$1(verticalProgress, 0, 1);
4708
+ if (clampedVerticalProgress < 0.2) {
4709
+ return palette.highlight;
4710
+ }
4711
+ if (clampedVerticalProgress < 0.45) {
4712
+ return palette.secondary;
4713
+ }
4714
+ if (clampedVerticalProgress < 0.8) {
4715
+ return palette.primary;
4716
+ }
4717
+ return `${palette.shadow}f2`;
4718
+ }
4719
+ /**
4720
+ * Draws one projected mesh patch with organic shading.
4721
+ *
4722
+ * @private helper of `octopus3dAvatarVisual`
4723
+ */
4724
+ function drawSurfacePatch(context, surfacePatch) {
4725
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
4726
+ if (surfacePatch.lightIntensity > 0) {
4727
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.15 * surfacePatch.lightIntensity})`);
4728
+ }
4729
+ else if (surfacePatch.lightIntensity < 0) {
4730
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
4731
+ }
4732
+ context.save();
4733
+ context.beginPath();
4734
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
4735
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
4736
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
4737
+ }
4738
+ context.closePath();
4739
+ context.strokeStyle = surfacePatch.outlineColor;
4740
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0044);
4741
+ context.lineJoin = 'round';
4742
+ context.stroke();
4743
+ context.restore();
4744
+ }
4745
+ /**
4746
+ * Creates the projected 3D tentacle strokes orbiting around the lower octopus body.
4747
+ *
4748
+ * @private helper of `octopus3dAvatarVisual`
4749
+ */
4750
+ function createOctopusTentacleStrokes(options) {
4751
+ const { createRandom, morphologyProfile, timeMs, size, center, radiusX, radiusY, radiusZ, rotationX, rotationY, sceneCenterX, sceneCenterY, animationPhase, } = options;
4752
+ return Array.from({ length: morphologyProfile.tentacles.count }, (_, tentacleIndex) => {
4753
+ const tentacleRandom = createRandom(`octopus3d-tentacle-${tentacleIndex}`);
4754
+ const spreadProgress = morphologyProfile.tentacles.count === 1 ? 0.5 : tentacleIndex / (morphologyProfile.tentacles.count - 1);
4755
+ const orbitAngle = -Math.PI * 0.92 + spreadProgress * Math.PI * 1.84 + (tentacleRandom() - 0.5) * 0.16;
4756
+ const flowLength = size * (0.19 + morphologyProfile.tentacles.flowLengthScale * 0.075 + tentacleRandom() * 0.018);
4757
+ const lateralReach = size *
4758
+ (0.08 + morphologyProfile.tentacles.lateralReachScale * 0.05 + Math.abs(Math.sin(orbitAngle)) * 0.018);
4759
+ const depthReach = size * (0.028 + morphologyProfile.tentacles.tipReachScale * 0.032);
4760
+ const sway = Math.sin(timeMs / (760 + tentacleIndex * 36) + animationPhase + tentacleRandom() * Math.PI * 2);
4761
+ const anchorPoint = {
4762
+ x: Math.sin(orbitAngle) * radiusX * (0.84 + tentacleRandom() * 0.08),
4763
+ y: radiusY * (0.22 + tentacleRandom() * 0.18),
4764
+ z: Math.cos(orbitAngle) * radiusZ * (0.72 + tentacleRandom() * 0.12),
4765
+ };
4766
+ const controlPointOne = {
4767
+ x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * 0.44,
4768
+ y: anchorPoint.y + flowLength * 0.26,
4769
+ z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.3 + sway * size * 0.012,
4770
+ };
4771
+ const controlPointTwo = {
4772
+ x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
4773
+ y: anchorPoint.y + flowLength * 0.66,
4774
+ z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.72 + sway * size * 0.02,
4775
+ };
4776
+ const endPoint = {
4777
+ x: anchorPoint.x +
4778
+ Math.sin(orbitAngle) * lateralReach * (1.02 + morphologyProfile.tentacles.tipWidthScale * 0.12) +
4779
+ sway * size * 0.028,
4780
+ y: anchorPoint.y + flowLength,
4781
+ z: anchorPoint.z + Math.cos(orbitAngle) * depthReach + sway * size * 0.016,
4782
+ };
4783
+ const scenePoints = Array.from({ length: 12 }, (_, sampleIndex) => transformScenePoint(sampleCubicBezierPoint3D(anchorPoint, controlPointOne, controlPointTwo, endPoint, sampleIndex / 11), center, rotationX, rotationY));
4784
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
4785
+ const averageDepth = scenePoints.reduce((depthSum, scenePoint) => depthSum + scenePoint.z, 0) / scenePoints.length;
4786
+ return {
4787
+ projectedPoints,
4788
+ averageDepth,
4789
+ isFrontFacing: averageDepth >= center.z - size * 0.006,
4790
+ baseWidth: size *
4791
+ (0.019 +
4792
+ morphologyProfile.tentacles.baseWidthScale * 0.007 +
4793
+ tentacleRandom() * 0.003 +
4794
+ Math.abs(Math.sin(orbitAngle)) * 0.002),
4795
+ tipWidth: size * (0.0046 + morphologyProfile.tentacles.tipWidthScale * 0.0018),
4796
+ colorBias: tentacleRandom(),
4797
+ };
4798
+ });
4799
+ }
4800
+ /**
4801
+ * Samples one point on a cubic Bezier curve in 3D.
4802
+ *
4803
+ * @private helper of `octopus3dAvatarVisual`
4804
+ */
4805
+ function sampleCubicBezierPoint3D(startPoint, controlPointOne, controlPointTwo, endPoint, progress) {
4806
+ const inverseProgress = 1 - progress;
4807
+ return {
4808
+ x: inverseProgress * inverseProgress * inverseProgress * startPoint.x +
4809
+ 3 * inverseProgress * inverseProgress * progress * controlPointOne.x +
4810
+ 3 * inverseProgress * progress * progress * controlPointTwo.x +
4811
+ progress * progress * progress * endPoint.x,
4812
+ y: inverseProgress * inverseProgress * inverseProgress * startPoint.y +
4813
+ 3 * inverseProgress * inverseProgress * progress * controlPointOne.y +
4814
+ 3 * inverseProgress * progress * progress * controlPointTwo.y +
4815
+ progress * progress * progress * endPoint.y,
4816
+ z: inverseProgress * inverseProgress * inverseProgress * startPoint.z +
4817
+ 3 * inverseProgress * inverseProgress * progress * controlPointOne.z +
4818
+ 3 * inverseProgress * progress * progress * controlPointTwo.z +
4819
+ progress * progress * progress * endPoint.z,
4820
+ };
4821
+ }
4822
+ /**
4823
+ * Draws one projected tentacle stroke with a slim highlight ridge.
4824
+ *
4825
+ * @private helper of `octopus3dAvatarVisual`
4826
+ */
4827
+ function drawTentacleStroke(context, tentacleStroke, palette) {
4828
+ const projectedSegments = tentacleStroke.projectedPoints.length - 1;
4829
+ for (let segmentIndex = 0; segmentIndex < projectedSegments; segmentIndex++) {
4830
+ const startPoint = tentacleStroke.projectedPoints[segmentIndex];
4831
+ const endPoint = tentacleStroke.projectedPoints[segmentIndex + 1];
4832
+ const progress = segmentIndex / projectedSegments;
4833
+ const width = tentacleStroke.baseWidth + (tentacleStroke.tipWidth - tentacleStroke.baseWidth) * progress;
4834
+ context.beginPath();
4835
+ context.moveTo(startPoint.x, startPoint.y);
4836
+ context.lineTo(endPoint.x, endPoint.y);
4837
+ context.strokeStyle =
4838
+ tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
4839
+ context.lineWidth = width;
4840
+ context.lineCap = 'round';
4841
+ context.stroke();
4842
+ context.beginPath();
4843
+ context.moveTo(startPoint.x, startPoint.y);
4844
+ context.lineTo(endPoint.x, endPoint.y);
4845
+ context.strokeStyle = tentacleStroke.isFrontFacing ? `${palette.highlight}80` : `${palette.highlight}40`;
4846
+ context.lineWidth = Math.max(1, width * 0.34);
4847
+ context.lineCap = 'round';
4848
+ context.stroke();
4849
+ }
4850
+ }
4851
+ /**
4852
+ * Resolves the front surface depth on an ellipsoid for one local face point.
4853
+ *
4854
+ * @private helper of `octopus3dAvatarVisual`
4855
+ */
4856
+ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
4857
+ const normalizedX = x / radiusX;
4858
+ const normalizedY = y / radiusY;
4859
+ const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
4860
+ return Math.sqrt(remainingDepthRatio) * radiusZ;
4861
+ }
4862
+ /**
4863
+ * Draws one projected eye on the turned octopus mantle.
4864
+ *
4865
+ * @private helper of `octopus3dAvatarVisual`
4866
+ */
4867
+ function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
4868
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
4869
+ if (centerScenePoint.z <= center.z) {
4870
+ return;
4871
+ }
4872
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
4873
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
4874
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
4875
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
4876
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
4877
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
4878
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
4879
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
4880
+ return;
4881
+ }
4882
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
4883
+ radiusX: projectedRadiusX,
4884
+ radiusY: projectedRadiusY,
4885
+ timeMs,
4886
+ phase,
4887
+ interaction,
4888
+ });
4889
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
4890
+ context.save();
4891
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
4892
+ context.rotate(rotation);
4893
+ context.beginPath();
4894
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
4895
+ context.fillStyle = '#f8fbff';
4896
+ context.fill();
4897
+ context.clip();
4898
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
4899
+ irisGradient.addColorStop(0, palette.highlight);
4900
+ irisGradient.addColorStop(0.56, palette.secondary);
4901
+ irisGradient.addColorStop(1, palette.shadow);
4902
+ context.beginPath();
4903
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
4904
+ context.fillStyle = irisGradient;
4905
+ context.fill();
4906
+ context.beginPath();
4907
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
4908
+ context.fillStyle = palette.ink;
4909
+ context.fill();
4910
+ context.beginPath();
4911
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
4912
+ context.fillStyle = '#ffffff';
4913
+ context.fill();
4914
+ context.restore();
4915
+ context.save();
4916
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
4917
+ context.rotate(rotation);
4918
+ context.beginPath();
4919
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
4920
+ context.strokeStyle = `${palette.shadow}cc`;
4921
+ context.lineWidth = projectedRadiusX * 0.16;
4922
+ context.stroke();
4923
+ context.beginPath();
4924
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
4925
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
4926
+ context.strokeStyle = `${palette.shadow}73`;
4927
+ context.lineWidth = projectedRadiusX * 0.14;
4928
+ context.lineCap = 'round';
4929
+ context.stroke();
4930
+ if (eyeStyle.lowerLidOpacity > 0) {
4931
+ context.beginPath();
4932
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
4933
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
4934
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
4935
+ context.lineWidth = projectedRadiusX * 0.08;
4936
+ context.lineCap = 'round';
4937
+ context.stroke();
4938
+ }
4939
+ context.restore();
4940
+ }
4941
+ /**
4942
+ * Draws a subtle projected mouth arc across the front of the mantle.
4943
+ *
4944
+ * @private helper of `octopus3dAvatarVisual`
4945
+ */
4946
+ function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
4947
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
4948
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
4949
+ return;
4950
+ }
4951
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
4952
+ context.beginPath();
4953
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
4954
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
4955
+ context.strokeStyle = `${palette.ink}b8`;
4956
+ context.lineWidth = Math.max(1.1, size * 0.009);
4957
+ context.lineCap = 'round';
4958
+ context.stroke();
4959
+ }
4960
+ /**
4961
+ * Draws one filled projected quad.
4962
+ *
4963
+ * @private helper of `octopus3dAvatarVisual`
4964
+ */
4965
+ function drawProjectedQuad(context, corners, fillStyle) {
4966
+ context.beginPath();
4967
+ context.moveTo(corners[0].x, corners[0].y);
4968
+ context.lineTo(corners[1].x, corners[1].y);
4969
+ context.lineTo(corners[2].x, corners[2].y);
4970
+ context.lineTo(corners[3].x, corners[3].y);
4971
+ context.closePath();
4972
+ context.fillStyle = fillStyle;
4973
+ context.fill();
4974
+ }
4975
+ /**
4976
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
4977
+ *
4978
+ * @private helper of `octopus3dAvatarVisual`
4979
+ */
4980
+ function formatAlphaHex(opacity) {
4981
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
4982
+ .toString(16)
4983
+ .padStart(2, '0');
4984
+ }
4985
+
4471
4986
  /* eslint-disable no-magic-numbers */
4472
4987
  /**
4473
4988
  * Octopus avatar visual.
@@ -5236,6 +5751,7 @@
5236
5751
  octopusAvatarVisual,
5237
5752
  octopus2AvatarVisual,
5238
5753
  octopus3AvatarVisual,
5754
+ octopus3dAvatarVisual,
5239
5755
  asciiOctopusAvatarVisual,
5240
5756
  minecraftAvatarVisual,
5241
5757
  minecraft2AvatarVisual,
@@ -11828,16 +12344,22 @@
11828
12344
  * Each teammate is listed with its tool name, TEAM instructions, and optional profile hints.
11829
12345
  */
11830
12346
  function buildTeamSystemMessageBody(teamEntries) {
11831
- const lines = [
11832
- ...TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES,
11833
- '',
11834
- ...teamEntries.map((entry, index) => {
11835
- const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
11836
- const detailLines = collectTeamEntryDetails(entry).map(formatTeamEntryDetailLine);
11837
- return [toolLine, ...detailLines].join('\n');
11838
- }),
11839
- ];
11840
- return lines.join('\n');
12347
+ const teammateSections = teamEntries.map((entry, index) => {
12348
+ const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
12349
+ const detailLines = collectTeamEntryDetails(entry).map(formatTeamEntryDetailLine);
12350
+ if (detailLines.length === 0) {
12351
+ return toolLine;
12352
+ }
12353
+ return spacetrim.spaceTrim((block) => `
12354
+ ${toolLine}
12355
+ ${block(detailLines.join('\n'))}
12356
+ `);
12357
+ });
12358
+ return spacetrim.spaceTrim((block) => `
12359
+ ${block(TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES.join('\n'))}
12360
+
12361
+ ${block(teammateSections.join('\n\n'))}
12362
+ `);
11841
12363
  }
11842
12364
  /**
11843
12365
  * Builds the model-visible description for one teammate tool.
@@ -40229,11 +40751,11 @@
40229
40751
  */
40230
40752
  function pipelineJsonToString(pipelineJson) {
40231
40753
  const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
40232
- let pipelineString = `# ${title}`;
40233
- if (description) {
40234
- pipelineString += '\n\n';
40235
- pipelineString += description;
40236
- }
40754
+ let pipelineString = spacetrim.spaceTrim((block) => `
40755
+ # ${title}
40756
+
40757
+ ${block(description || '')}
40758
+ `);
40237
40759
  const commands = [];
40238
40760
  if (pipelineUrl) {
40239
40761
  commands.push(`PIPELINE URL ${pipelineUrl}`);
@@ -40249,20 +40771,17 @@
40249
40771
  for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
40250
40772
  commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
40251
40773
  }
40252
- pipelineString += '\n\n';
40253
- pipelineString += commands.map((command) => `- ${command}`).join('\n');
40774
+ pipelineString = spacetrim.spaceTrim((block) => `
40775
+ ${block(pipelineString)}
40776
+
40777
+ ${block(commands.map((command) => `- ${command}`).join('\n'))}
40778
+ `);
40254
40779
  for (const task of tasks) {
40255
40780
  const {
40256
40781
  /* Note: Not using:> name, */
40257
40782
  title, description,
40258
40783
  /* Note: dependentParameterNames, */
40259
40784
  jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
40260
- pipelineString += '\n\n';
40261
- pipelineString += `## ${title}`;
40262
- if (description) {
40263
- pipelineString += '\n\n';
40264
- pipelineString += description;
40265
- }
40266
40785
  const commands = [];
40267
40786
  let contentLanguage = 'text';
40268
40787
  if (taskType === 'PROMPT_TASK') {
@@ -40325,18 +40844,23 @@
40325
40844
  commands.push(`FORMAT JSON`);
40326
40845
  }
40327
40846
  } /* not else */
40328
- pipelineString += '\n\n';
40329
- pipelineString += commands.map((command) => `- ${command}`).join('\n');
40330
- pipelineString += '\n\n';
40331
- pipelineString += '```' + contentLanguage;
40332
- pipelineString += '\n';
40333
- pipelineString += spacetrim.spaceTrim(content);
40334
- // <- TODO: [main] !!3 Escape
40335
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
40336
- pipelineString += '\n';
40337
- pipelineString += '```';
40338
- pipelineString += '\n\n';
40339
- pipelineString += `\`-> {${resultingParameterName}}\``; // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
40847
+ pipelineString = spacetrim.spaceTrim((block) => `
40848
+ ${block(pipelineString)}
40849
+
40850
+ ## ${title}
40851
+
40852
+ ${block(description || '')}
40853
+
40854
+ ${block(commands.map((command) => `- ${command}`).join('\n'))}
40855
+
40856
+ \`\`\`${contentLanguage}
40857
+ ${block(spacetrim.spaceTrim(content))}
40858
+ \`\`\`
40859
+
40860
+ \`-> {${resultingParameterName}}\`
40861
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
40862
+ // <- TODO: [main] !!3 Escape
40863
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
40340
40864
  }
40341
40865
  return validatePipelineString(pipelineString);
40342
40866
  }
@@ -45405,7 +45929,11 @@
45405
45929
  if (examples.length === 0) {
45406
45930
  return null;
45407
45931
  }
45408
- return `## Sample of communication with the agent:\n\n${examples.join('\n\n')}`;
45932
+ return spacetrim.spaceTrim((block) => `
45933
+ ## Sample of communication with the agent:
45934
+
45935
+ ${block(examples.join('\n\n'))}
45936
+ `);
45409
45937
  }
45410
45938
  /**
45411
45939
  * Collects the individual lines used in the example interaction section.
@@ -45442,7 +45970,11 @@
45442
45970
  function appendSystemMessageSection(requirements, section) {
45443
45971
  return {
45444
45972
  ...requirements,
45445
- systemMessage: requirements.systemMessage + '\n\n' + section,
45973
+ systemMessage: spacetrim.spaceTrim((block) => `
45974
+ ${block(requirements.systemMessage)}
45975
+
45976
+ ${block(section)}
45977
+ `),
45446
45978
  };
45447
45979
  }
45448
45980
  /**