@promptbook/remote-server 0.112.0-117 → 0.112.0-118

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.
@@ -11,6 +11,11 @@ import type { CliAgentThinkingLevel } from '../book-3.0/CliAgent';
11
11
  import type { CliAgentRunOptions } from '../book-3.0/CliAgent';
12
12
  import type { CliAgentOptions } from '../book-3.0/CliAgent';
13
13
  import { CliAgent } from '../book-3.0/CliAgent';
14
+ import { CLI_AGENT_HARNESS_NAMES } from '../book-3.0/cliAgentEnv';
15
+ import { CLI_AGENT_THINKING_LEVEL_VALUES } from '../book-3.0/cliAgentEnv';
16
+ import { PTBK_HARNESS_ENV } from '../book-3.0/cliAgentEnv';
17
+ import { PTBK_MODEL_ENV } from '../book-3.0/cliAgentEnv';
18
+ import { PTBK_THINKING_LEVEL_ENV } from '../book-3.0/cliAgentEnv';
14
19
  import type { LiteAgentOptions } from '../book-3.0/LiteAgent';
15
20
  import type { LiteAgentRunOptions } from '../book-3.0/LiteAgent';
16
21
  import { LiteAgent } from '../book-3.0/LiteAgent';
@@ -39,6 +44,11 @@ export type { CliAgentThinkingLevel };
39
44
  export type { CliAgentRunOptions };
40
45
  export type { CliAgentOptions };
41
46
  export { CliAgent };
47
+ export { CLI_AGENT_HARNESS_NAMES };
48
+ export { CLI_AGENT_THINKING_LEVEL_VALUES };
49
+ export { PTBK_HARNESS_ENV };
50
+ export { PTBK_MODEL_ENV };
51
+ export { PTBK_THINKING_LEVEL_ENV };
42
52
  export type { LiteAgentOptions };
43
53
  export type { LiteAgentRunOptions };
44
54
  export { LiteAgent };
@@ -15,7 +15,7 @@ export type BookNodeAgentSource = Book | string_book;
15
15
  */
16
16
  export type BookNodeAgentSourceOptions = {
17
17
  readonly agentPath?: string;
18
- readonly book?: BookNodeAgentSource;
18
+ readonly book?: string | BookNodeAgentSource;
19
19
  readonly currentWorkingDirectory?: string;
20
20
  };
21
21
  /**
@@ -1,16 +1,17 @@
1
1
  import type { BookNodeAgentSourceOptions } from './BookNodeAgentSource';
2
+ import { CLI_AGENT_HARNESS_NAMES, CLI_AGENT_THINKING_LEVEL_VALUES } from './cliAgentEnv';
2
3
  /**
3
4
  * CLI harness names supported by `ptbk agent exec`.
4
5
  *
5
6
  * @public exported from `@promptbook/node`
6
7
  */
7
- export type CliAgentHarness = 'openai-codex' | 'github-copilot' | 'cline' | 'claude-code' | 'opencode' | 'gemini';
8
+ export type CliAgentHarness = (typeof CLI_AGENT_HARNESS_NAMES)[number];
8
9
  /**
9
10
  * Thinking levels supported by CLI coding harnesses.
10
11
  *
11
12
  * @public exported from `@promptbook/node`
12
13
  */
13
- export type CliAgentThinkingLevel = 'low' | 'medium' | 'high' | 'xhigh';
14
+ export type CliAgentThinkingLevel = (typeof CLI_AGENT_THINKING_LEVEL_VALUES)[number];
14
15
  /**
15
16
  * Per-run CLI options exposed by `CliAgent`.
16
17
  *
@@ -22,6 +23,7 @@ export type CliAgentRunOptions = {
22
23
  readonly allowCredits?: boolean;
23
24
  readonly context?: string;
24
25
  readonly harness?: CliAgentHarness;
26
+ readonly isVerbose?: boolean;
25
27
  readonly model?: string;
26
28
  readonly noUi?: boolean;
27
29
  readonly thinkingLevel?: CliAgentThinkingLevel;
@@ -38,6 +40,9 @@ export type CliAgentOptions = BookNodeAgentSourceOptions & CliAgentRunOptions;
38
40
  * It uses the same harnesses and execution path as `ptbk agent exec`, running the runner
39
41
  * in-process instead of spawning a separate CLI process.
40
42
  *
43
+ * When no `harness` is provided in the constructor or per-run options, `CliAgent` falls back
44
+ * to the `PTBK_HARNESS` environment variable, mirroring `ptbk agent exec` behavior.
45
+ *
41
46
  * @public exported from `@promptbook/node`
42
47
  */
43
48
  export declare class CliAgent {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * All CLI harness names supported by `CliAgent` and `ptbk agent exec`.
3
+ *
4
+ * @public exported from `@promptbook/node`
5
+ */
6
+ export declare const CLI_AGENT_HARNESS_NAMES: readonly ["openai-codex", "github-copilot", "cline", "claude-code", "opencode", "gemini"];
7
+ /**
8
+ * All supported thinking-level values for CLI coding-agent runners.
9
+ *
10
+ * @public exported from `@promptbook/node`
11
+ */
12
+ export declare const CLI_AGENT_THINKING_LEVEL_VALUES: readonly ["low", "medium", "high", "xhigh"];
13
+ /**
14
+ * Environment variable used as the default runner identifier when `--harness` is omitted or not set in `CliAgent`.
15
+ *
16
+ * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`)
17
+ * so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
18
+ *
19
+ * @public exported from `@promptbook/node`
20
+ */
21
+ export declare const PTBK_HARNESS_ENV = "PTBK_HARNESS";
22
+ /**
23
+ * Environment variable used as the default runner model when `--model` is omitted or not set in `CliAgent`.
24
+ *
25
+ * @public exported from `@promptbook/node`
26
+ */
27
+ export declare const PTBK_MODEL_ENV = "PTBK_MODEL";
28
+ /**
29
+ * Environment variable used as the default thinking level when `--thinking-level` is omitted or not set in `CliAgent`.
30
+ *
31
+ * @public exported from `@promptbook/node`
32
+ */
33
+ export declare const PTBK_THINKING_LEVEL_ENV = "PTBK_THINKING_LEVEL";
@@ -1,29 +1,13 @@
1
1
  import { Command as Program } from 'commander';
2
2
  import type { ThinkingLevel } from '../coder/ThinkingLevel';
3
+ import { PTBK_HARNESS_ENV, PTBK_MODEL_ENV, PTBK_THINKING_LEVEL_ENV } from '../../../book-3.0/cliAgentEnv';
4
+ export { PTBK_HARNESS_ENV, PTBK_MODEL_ENV, PTBK_THINKING_LEVEL_ENV };
3
5
  /**
4
6
  * Runner identifiers supported by Promptbook CLI agent orchestration commands.
5
7
  *
6
8
  * @private internal utility of `promptbookCli`
7
9
  */
8
10
  export declare const PROMPT_RUNNER_HARNESS_NAMES: readonly ["openai-codex", "github-copilot", "cline", "claude-code", "opencode", "gemini"];
9
- /**
10
- * Environment variable used as the default runner identifier when `--harness` is omitted.
11
- *
12
- * @private internal utility of `promptbookCli`
13
- */
14
- export declare const PTBK_HARNESS_ENV = "PTBK_HARNESS";
15
- /**
16
- * Environment variable used as the default runner model when `--model` is omitted.
17
- *
18
- * @private internal utility of `promptbookCli`
19
- */
20
- export declare const PTBK_MODEL_ENV = "PTBK_MODEL";
21
- /**
22
- * Environment variable used as the default runner thinking level when `--thinking-level` is omitted.
23
- *
24
- * @private internal utility of `promptbookCli`
25
- */
26
- export declare const PTBK_THINKING_LEVEL_ENV = "PTBK_THINKING_LEVEL";
27
11
  /**
28
12
  * Runner identifier supported by Promptbook CLI agent orchestration commands.
29
13
  *
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.112.0-116`).
18
+ * It follows semantic versioning (e.g., `0.112.0-117`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/remote-server",
3
- "version": "0.112.0-117",
3
+ "version": "0.112.0-118",
4
4
  "description": "Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -99,7 +99,7 @@
99
99
  "types": "./esm/src/_packages/remote-server.index.d.ts",
100
100
  "typings": "./esm/src/_packages/remote-server.index.d.ts",
101
101
  "peerDependencies": {
102
- "@promptbook/core": "0.112.0-117"
102
+ "@promptbook/core": "0.112.0-118"
103
103
  },
104
104
  "dependencies": {
105
105
  "@mozilla/readability": "0.6.0",
package/umd/index.umd.js CHANGED
@@ -53,7 +53,7 @@
53
53
  * @generated
54
54
  * @see https://github.com/webgptorg/promptbook
55
55
  */
56
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-117';
56
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-118';
57
57
  /**
58
58
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
59
59
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -14253,21 +14253,22 @@
14253
14253
  * @private helper of `fractalAvatarVisual`
14254
14254
  */
14255
14255
  function drawDragonCurveLayer(context, points, options) {
14256
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
14256
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
14257
14257
  const firstPoint = points[0];
14258
14258
  const lastPoint = points[points.length - 1];
14259
14259
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
14260
14260
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
14261
14261
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
14262
14262
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
14263
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
14264
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
14263
14265
  context.save();
14264
14266
  context.beginPath();
14265
14267
  tracePolyline(context, points);
14266
- context.strokeStyle = `${shadowColor}82`;
14267
- context.lineWidth = strokeWidth * 1.8;
14268
+ context.strokeStyle = `${shadowColor}48`;
14269
+ context.lineWidth = strokeWidth * 4.5;
14268
14270
  context.lineJoin = 'round';
14269
14271
  context.lineCap = 'round';
14270
- context.filter = `blur(${size * 0.022}px)`;
14271
14272
  context.stroke();
14272
14273
  context.restore();
14273
14274
  context.beginPath();
@@ -14851,11 +14852,23 @@
14851
14852
  * @private helper of `minecraft2AvatarVisual`
14852
14853
  */
14853
14854
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
14855
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
14856
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
14857
+ const rx = size * (0.16 + interaction.intensity * 0.015);
14858
+ const ry = size * 0.055;
14859
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
14854
14860
  context.save();
14855
- context.fillStyle = `${palette.shadow}66`;
14856
- context.filter = `blur(${size * 0.02}px)`;
14861
+ context.translate(cx, cy);
14862
+ context.scale(1, ry / rx);
14863
+ const blurRadius = rx * 1.4;
14864
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
14865
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
14866
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
14867
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
14868
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
14869
+ context.fillStyle = shadowGradient;
14857
14870
  context.beginPath();
14858
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.03, size * 0.85 + Math.sin(timeMs / 880) * size * 0.01, size * (0.16 + interaction.intensity * 0.015), size * 0.055, 0, 0, Math.PI * 2);
14871
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
14859
14872
  context.fill();
14860
14873
  context.restore();
14861
14874
  }
@@ -15079,13 +15092,27 @@
15079
15092
  spotlight.addColorStop(1, `${palette.highlight}00`);
15080
15093
  context.fillStyle = spotlight;
15081
15094
  context.fillRect(0, 0, size, size);
15082
- context.save();
15083
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
15084
- context.filter = `blur(${size * 0.018}px)`;
15085
- context.beginPath();
15086
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
15087
- context.fill();
15088
- context.restore();
15095
+ {
15096
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
15097
+ const cx = size * 0.5;
15098
+ const cy = size * 0.86;
15099
+ const rx = size * 0.2;
15100
+ const ry = size * 0.06;
15101
+ const blurRadius = rx * 1.4;
15102
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15103
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
15104
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
15105
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
15106
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
15107
+ context.save();
15108
+ context.translate(cx, cy);
15109
+ context.scale(1, ry / rx);
15110
+ context.fillStyle = shadowGradient;
15111
+ context.beginPath();
15112
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15113
+ context.fill();
15114
+ context.restore();
15115
+ }
15089
15116
  drawVoxelCuboid(context, {
15090
15117
  x: bodyX,
15091
15118
  y: bodyY,
@@ -16137,6 +16164,35 @@
16137
16164
  y: -0.62,
16138
16165
  z: 0.94,
16139
16166
  });
16167
+ /**
16168
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
16169
+ *
16170
+ * @private helper of `octopus3dAvatarVisual`
16171
+ */
16172
+ const octopus3dStableStateCache = new WeakMap();
16173
+ /**
16174
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
16175
+ *
16176
+ * @private helper of `octopus3dAvatarVisual`
16177
+ */
16178
+ function getOctopus3dStableState(createRandom) {
16179
+ const cached = octopus3dStableStateCache.get(createRandom);
16180
+ if (cached !== undefined) {
16181
+ return cached;
16182
+ }
16183
+ const animationRandom = createRandom('octopus3d-animation-profile');
16184
+ const eyeRandom = createRandom('octopus3d-eye-profile');
16185
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
16186
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
16187
+ const state = {
16188
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
16189
+ animationPhase: animationRandom() * Math.PI * 2,
16190
+ leftEyePhaseOffset,
16191
+ rightEyePhaseOffset,
16192
+ };
16193
+ octopus3dStableStateCache.set(createRandom, state);
16194
+ return state;
16195
+ }
16140
16196
  /**
16141
16197
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
16142
16198
  *
@@ -16149,10 +16205,7 @@
16149
16205
  isAnimated: true,
16150
16206
  supportsPointerTracking: true,
16151
16207
  render({ context, size, palette, createRandom, timeMs, interaction }) {
16152
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16153
- const animationRandom = createRandom('octopus3d-animation-profile');
16154
- const eyeRandom = createRandom('octopus3d-eye-profile');
16155
- const animationPhase = animationRandom() * Math.PI * 2;
16208
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
16156
16209
  const sceneCenterX = size * 0.5;
16157
16210
  const sceneCenterY = size * 0.56;
16158
16211
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -16249,12 +16302,12 @@
16249
16302
  x: -faceEyeSpacing,
16250
16303
  y: faceEyeYOffset,
16251
16304
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
16252
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
16305
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16253
16306
  drawProjectedOrganicEye(context, {
16254
16307
  x: faceEyeSpacing,
16255
16308
  y: faceEyeYOffset,
16256
16309
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
16257
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
16310
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16258
16311
  drawProjectedOrganicMouth(context, [
16259
16312
  {
16260
16313
  x: -mouthHalfWidth,
@@ -16298,14 +16351,28 @@
16298
16351
  /**
16299
16352
  * Draws the soft ground shadow below the octopus.
16300
16353
  *
16354
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
16355
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
16356
+ *
16301
16357
  * @private helper of `octopus3dAvatarVisual`
16302
16358
  */
16303
16359
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
16360
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
16361
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
16362
+ const rx = size * (0.18 + interaction.intensity * 0.02);
16363
+ const ry = size * 0.06;
16304
16364
  context.save();
16305
- context.fillStyle = `${palette.shadow}66`;
16306
- context.filter = `blur(${size * 0.022}px)`;
16365
+ context.translate(cx, cy);
16366
+ context.scale(1, ry / rx);
16367
+ const blurRadius = rx * 1.4;
16368
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16369
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16370
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16371
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16372
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16373
+ context.fillStyle = shadowGradient;
16307
16374
  context.beginPath();
16308
- 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);
16375
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16309
16376
  context.fill();
16310
16377
  context.restore();
16311
16378
  }
@@ -16536,6 +16603,35 @@
16536
16603
  y: -0.6,
16537
16604
  z: 0.98,
16538
16605
  });
16606
+ /**
16607
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
16608
+ *
16609
+ * @private helper of `octopus3d2AvatarVisual`
16610
+ */
16611
+ const octopus3d2StableStateCache = new WeakMap();
16612
+ /**
16613
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
16614
+ *
16615
+ * @private helper of `octopus3d2AvatarVisual`
16616
+ */
16617
+ function getOctopus3d2StableState(createRandom) {
16618
+ const cached = octopus3d2StableStateCache.get(createRandom);
16619
+ if (cached !== undefined) {
16620
+ return cached;
16621
+ }
16622
+ const animationRandom = createRandom('octopus3d2-animation-profile');
16623
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
16624
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
16625
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
16626
+ const state = {
16627
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
16628
+ animationPhase: animationRandom() * Math.PI * 2,
16629
+ leftEyePhaseOffset,
16630
+ rightEyePhaseOffset,
16631
+ };
16632
+ octopus3d2StableStateCache.set(createRandom, state);
16633
+ return state;
16634
+ }
16539
16635
  /**
16540
16636
  * Octopus 3D 2 avatar visual.
16541
16637
  *
@@ -16548,10 +16644,7 @@
16548
16644
  isAnimated: true,
16549
16645
  supportsPointerTracking: true,
16550
16646
  render({ context, size, palette, createRandom, timeMs, interaction }) {
16551
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16552
- const animationRandom = createRandom('octopus3d2-animation-profile');
16553
- const eyeRandom = createRandom('octopus3d2-eye-profile');
16554
- const animationPhase = animationRandom() * Math.PI * 2;
16647
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
16555
16648
  const sceneCenterX = size * 0.5;
16556
16649
  const sceneCenterY = size * 0.575;
16557
16650
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -16604,8 +16697,8 @@
16604
16697
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
16605
16698
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
16606
16699
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
16607
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
16608
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
16700
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16701
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16609
16702
  drawProjectedOrganicMouth(context, [
16610
16703
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
16611
16704
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -16634,14 +16727,28 @@
16634
16727
  /**
16635
16728
  * Draws the soft floor shadow that anchors the single mesh in the frame.
16636
16729
  *
16730
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
16731
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
16732
+ *
16637
16733
  * @private helper of `octopus3d2AvatarVisual`
16638
16734
  */
16639
16735
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
16736
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
16737
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
16738
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
16739
+ const ry = size * 0.062;
16640
16740
  context.save();
16641
- context.fillStyle = `${palette.shadow}66`;
16642
- context.filter = `blur(${size * 0.024}px)`;
16741
+ context.translate(cx, cy);
16742
+ context.scale(1, ry / rx);
16743
+ const blurRadius = rx * 1.4;
16744
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16745
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16746
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16747
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16748
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16749
+ context.fillStyle = shadowGradient;
16643
16750
  context.beginPath();
16644
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.88 + Math.sin(timeMs / 940) * size * 0.008, size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018), size * 0.062, 0, 0, Math.PI * 2);
16751
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16645
16752
  context.fill();
16646
16753
  context.restore();
16647
16754
  }
@@ -16797,6 +16904,40 @@
16797
16904
  * @private helper of `octopus3d3AvatarVisual`
16798
16905
  */
16799
16906
  const OCTOPUS_TENTACLE_COUNT = 8;
16907
+ /**
16908
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
16909
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
16910
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
16911
+ *
16912
+ * @private helper of `octopus3d3AvatarVisual`
16913
+ */
16914
+ const stableStateCache = new WeakMap();
16915
+ /**
16916
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
16917
+ * result on every subsequent call within the same `<Avatar/>` mount.
16918
+ *
16919
+ * @private helper of `octopus3d3AvatarVisual`
16920
+ */
16921
+ function getOctopus3d3StableState(createRandom) {
16922
+ const cached = stableStateCache.get(createRandom);
16923
+ if (cached !== undefined) {
16924
+ return cached;
16925
+ }
16926
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16927
+ const animationRandom = createRandom('octopus3d3-animation-profile');
16928
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
16929
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
16930
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
16931
+ const state = {
16932
+ morphologyProfile,
16933
+ animationPhase: animationRandom() * Math.PI * 2,
16934
+ leftEyePhaseOffset,
16935
+ rightEyePhaseOffset,
16936
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
16937
+ };
16938
+ stableStateCache.set(createRandom, state);
16939
+ return state;
16940
+ }
16800
16941
  /**
16801
16942
  * Octopus 3D 3 avatar visual.
16802
16943
  *
@@ -16809,11 +16950,7 @@
16809
16950
  isAnimated: true,
16810
16951
  supportsPointerTracking: true,
16811
16952
  render({ context, size, palette, createRandom, timeMs, interaction }) {
16812
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16813
- const animationRandom = createRandom('octopus3d3-animation-profile');
16814
- const eyeRandom = createRandom('octopus3d3-eye-profile');
16815
- const animationPhase = animationRandom() * Math.PI * 2;
16816
- const tentacleProfiles = createContinuousTentacleProfiles(createRandom, morphologyProfile);
16953
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
16817
16954
  const sceneCenterX = size * 0.5;
16818
16955
  const sceneCenterY = size * 0.535;
16819
16956
  const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
@@ -16890,8 +17027,8 @@
16890
17027
  size,
16891
17028
  palette,
16892
17029
  });
16893
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
16894
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
17030
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17031
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16895
17032
  drawProjectedOrganicMouth(context, [
16896
17033
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
16897
17034
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -16942,14 +17079,30 @@
16942
17079
  /**
16943
17080
  * Draws the soft lower shadow that anchors the octopus in the avatar frame.
16944
17081
  *
17082
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
17083
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
17084
+ *
16945
17085
  * @private helper of `octopus3d3AvatarVisual`
16946
17086
  */
16947
17087
  function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
17088
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
17089
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
17090
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
17091
+ const ry = size * 0.06;
17092
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
17093
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
16948
17094
  context.save();
16949
- context.fillStyle = `${palette.shadow}66`;
16950
- context.filter = `blur(${size * 0.025}px)`;
17095
+ context.translate(cx, cy);
17096
+ context.scale(1, ry / rx);
17097
+ const blurRadius = rx * 1.4;
17098
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
17099
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
17100
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
17101
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
17102
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
17103
+ context.fillStyle = shadowGradient;
16951
17104
  context.beginPath();
16952
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.9 + Math.sin(timeMs / 980) * size * 0.007, size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02), size * 0.06, 0, 0, Math.PI * 2);
17105
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16953
17106
  context.fill();
16954
17107
  context.restore();
16955
17108
  }