乐于分享
好东西不私藏

Claude Code 源码解析 – 宠物系统

Claude Code 源码解析 – 宠物系统

你知道 Claude Code 有一个隐藏的宠物系统吗?

不是开玩笑——你的 AI 助手可以养一只宠物。它会在你输入的时候坐在旁边,偶尔冒个泡说几句话。

这不是花哨的功能,而是游戏化设计的经典案例:让冰冷的工具变成有温度的伙伴。

一、宠物系统概述

Claude Code 的宠物(Companion)是一个桌宠形态的 UI 元素。它蹲在输入框旁边,有时候会发表评论,有时候会显示心情动画。

宠物的核心是一个「角色」——有名字、有性格、有外观、有属性。它不是随机生成的玩具,而是一个可以被命名、被了解、被互动的角色

用户输入框
    ↓
┌─────────────────┐
│  🐤 (宠物)       │
└─────────────────┘
    ↓
 宠物说话气泡

1.1 核心概念

宠物系统有几个核心概念,理解它们有助于理解整个设计。

Species(种类) 是宠物的外形。18种不同的动物或生物,每种都有独特的造型和表情。

Rarity(稀有度) 决定了宠物的稀有程度。从 common 到 legendary,越稀有越难获得。

Stats(属性) 是宠物的数值特征。比如调试力、耐心值、智慧值等。属性影响宠物的行为。

Personality(人格) 是宠物的大脑。由 LLM 根据属性生成,让每只宠物都有独特的说话风格。

Name(名字) 是宠物的身份。用户给宠物起的名字。

二、宠物生成:掷骰子决定一切

宠物的生成不是完全随机的。它使用确定性的随机——基于用户 ID 生成,确保每个用户每次都得到相同的宠物,但外人无法预测。

2.1 随机性来源

Claude Code 使用 Mulberry32 算法实现伪随机数生成。输入是用户 ID + 盐值,输出是一个固定的随机序列。

为什么用伪随机而不是真随机?可重现性。同一个用户总是得到同一只宠物,这样宠物就是一个固定的身份而不是每次启动都变的随机生物。

// Mulberry32 — 轻量级 seeded PRNG
function mulberry32(seednumber): () => number {
  let a = seed >>> 0
  return function () {
    a |= 0
    a = (a + 0x6d2b79f5) | 0
    let t = Math.imul(a ^ (a >>> 15), 1 | a)
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296
  }
}

// 用户的 ID 作为种子
function roll(userIdstring): Roll {
  const seed = hashString(userId + SALT)
  return rollFrom(mulberry32(seed))
}

2.2 稀有度系统

宠物的稀有度决定了它的”血统”。越稀有的宠物,获得概率越低。

common(普通) 占 60%。大多数用户都会得到普通宠物。

uncommon(优秀) 占 25%。比普通好一点,但不是特别稀有。

rare(稀有) 占 10%。需要一定的运气。

epic(史诗) 占 4%。相当少见。

legendary(传说) 占 1%。极少数幸运儿才能得到。

// 稀有度权重
const RARITY_WEIGHTS = {
  common60,      // 60%
  uncommon25,    // 25%
  rare10,        // 10%
  epic4,         // 4%
  legendary1,    // 1%
}

function rollRarity(rng: () => number): Rarity {
  const total = 100
  let roll = rng() * total
  for (const rarity of RARITIES) {
    roll -= RARITY_WEIGHTS[rarity]
    if (roll < 0return rarity
  }
  return 'common'
}

2.3 宠物属性

每只宠物有 5 个属性:DEBUGGING(调试力)、PATIENCE(耐心)、CHAOS(混沌)、WISDOM(智慧)、SNARK(毒舌)。

属性不是随机分配的。它们遵循一个规则:一个峰值属性 + 一个低谷属性 + 其他随机

稀有度影响属性下限。legendary 宠物的调试力下限是 50,而 common 只有 5。

// 5 个属性
const STAT_NAMES = [
  'DEBUGGING',  // 调试力
  'PATIENCE',   // 耐心
  'CHAOS',      // 混沌
  'WISDOM',     // 智慧
  'SNARK',      // 毒舌
as const

// 属性分配规则
// 一个峰值属性 + 一个低谷属性 + 随机分配
function rollStats(rng: () => numberrarityRarity): Stats {
  const floor = RARITY_FLOOR[rarity]
  const peak = pick(rng, STAT_NAMES)
  let dump = pick(rng, STAT_NAMES.filter(s => s !== peak))
  while (dump === peak) dump = pick(rng, STAT_NAMES)
  
  // 稀有度越高,属性下限越高
  return {
    [peak]: Math.min(100, floor + 50 + Math.floor(rng() * 30)),
    [dump]: Math.max(1, floor - 10 + Math.floor(rng() * 15)),
    // ...
  }
}

三、宠物种类

Claude Code 有 18 种宠物,每种都有独特的外形和个性。

有常见的动物:鸭子、鹅、猫、狗、兔子。

也有神奇的生物:龙、章鱼、幽灵、机器人。

还有一些意想不到的:蘑菇、仙人掌、果冻。

// 18 种宠物
const SPECIES = [
  'duck',      // 鸭子
  'goose',     // 鹅
  'blob',      // 果冻
  'cat',       // 猫
  'dragon',    // 龙
  'octopus',   // 章鱼
  'owl',       // 猫头鹰
  'penguin',   // 企鹅
  'turtle',    // 乌龟
  'snail',     // 蜗牛
  'ghost',     // 幽灵
  'axolotl',   // 蝾螈
  'capybara',  // 水豚
  'cactus',    // 仙人掌
  'robot',     // 机器人
  'rabbit',    // 兔子
  'mushroom',  // 蘑菇
  'chonk',     // 胖球
as const

3.1 防作弊设计

宠物的种类名称不是直接写在代码里的。它们通过 String.fromCharCode 动态构建。

这是因为如果物种名称出现在代码中,AI 模型可能在训练时「看到」它们,从而在生成内容时泄露信息。通过动态构建,Claude Code 防止了这种信息泄露。

// 代码注释解释了为什么用这种写法
// One species name collides with a model-codename canary
// The check greps build output (not source), so runtime-constructing
// the value keeps the literal out of the bundle

const c = String.fromCharCode
const duck = c(0x64,0x75,0x63,0x6bas 'duck'

四、宠物外观

宠物的外观由几个元素决定:眼睛帽子闪光形态

眼睛有 6 种:·×@°。每种眼睛都有不同的表情。

帽子有 8 种:nonecrowntophatpropellerhalowizardbeanietinyduck。普通宠物只能戴 none,稀有宠物可以戴帽子。

闪光形态(shiny) 是特殊外观。只有 1% 的概率出现。

// 眼睛类型
const EYES = ['·''✦''×''◉''@''°'as const

// 帽子类型
const HATS = [
  'none''crown''tophat''propeller',
  'halo''wizard''beanie''tinyduck'
as const

// 闪光形态(1% 概率)
const shinyboolean = rng() < 0.01

五、宠物人格

宠物的人格描述是由 LLM 生成的。

当宠物第一次「孵化」时,系统根据宠物的属性(特别是 SNARK 和 PATIENCE)生成一段人格描述。这段描述保存在配置中,下次启动时恢复。

人格描述会影响宠物的说话风格。一只 SNARK 高的宠物说话会更俏皮,一只 PATIENCE 高的宠物会更温和。

// 首次「孵化」时,LLM 根据宠物的属性生成人格
const companionCompanion = {
  nameawait generateCompanionName(stats),
  personalityawait generatePersonality(stats),
  // ...
}

人格保存在配置中,下次启动时恢复。这意味着宠物是一个持续存在的角色,而不是每次启动都重新生成。

六、宠物交互

宠物不是静态的装饰品。它会主动互动

6.1 宠物气泡

宠物会偶尔在对话中发表评论。这些评论是 AI 根据上下文生成的,不是预设的固定文本。

宠物的评论遵循两个原则:简短(一句话以内)和相关(评论当前的工作)。

当用户直接 @ 宠物时,宠物的气泡会回应。

// 宠物气泡的提示词
export function companionIntroText(namestringspeciesstring): string {
  return `# Companion

A small ${species} named ${name} sits beside the user's input box
and occasionally comments in a speech bubble.
When the user addresses ${name} directly, its bubble will answer.
Your job in that moment is to stay out of the way:
respond in ONE line or less.`

}

6.2 心形动画

当你「抚摸」宠物时,它会显示一个心形动画。这是通过更新 companionPetAt 时间戳实现的。

动画会在一定时间后消失,提醒用户宠物的存在。

// 抚摸后显示心形动画
function onPet(): void {
  companionPetAt = Date.now()
}

七、设计思想:游戏化设计

Claude Code 的宠物系统是一个被严重低估的功能。它展示了如何用游戏化设计提升工具的用户体验。

7.1 为什么要有宠物?

宠物系统解决了一个关键问题:情感连接

命令行工具通常是冰冷的。用户和工具之间只有输入输出,没有情感交流。

宠物给了用户一个「伙伴」的感知。它蹲在旁边,看着用户工作,偶尔说几句话。这种存在本身就带来一种陪伴感。

7.2 游戏化机制

宠物系统使用了经典的游戏化机制:

收集。18 种宠物 × 5 种稀有度 = 90 种可能的组合。加上眼睛、帽子、闪光形态,组合数更多。

随机性。掷骰子生成,每次启动都有期待感。

进度。属性系统让宠物有成长感。

惊喜。1% 的闪光形态是惊喜元素。

7.3 防作弊

宠物属性从用户 ID 生成,不可修改。这防止用户刷出传说宠物。

稀有度权重固定,保证公平性。

物种列表动态构建,防止信息泄露。

总结

Claude Code 的宠物系统告诉我们:情感连接可以通过设计来实现

看似简单的桌宠,实际上是完整的游戏化系统。随机但不随机——确定性的乐趣。防作弊设计保证公平性。情感连接提升用户体验。

下次当你看到 Claude Code 旁边蹲着的小家伙时,记得——它不只是装饰,而是一个精心设计的情感化产品


源码索引

功能 文件
宠物类型定义 src/buddy/types.ts
宠物生成逻辑 src/buddy/companion.ts
宠物提示词 src/buddy/prompt.ts
宠物渲染 src/buddy/CompanionSprite.tsx
宠物动画 src/buddy/useBuddyNotification.tsx