重置buddy脚本:https://github.com/Fzuim/reset-buddy.git

1. 功能概述

Buddy 是 Claude Code 内置的虚拟宠物系统。用户通过 /buddy 命令"孵化"一只 ASCII 小宠物,它会蹲在终端输入框旁边,偶尔通过气泡发表评论,为编程过程增添趣味。

系统具有以下特征:

  • 18 种物种:duck、goose、blob、cat、dragon、octopus、owl、penguin、turtle、snail、ghost、axolotl、capybara、cactus、robot、rabbit、mushroom、chonk
  • 5 个稀有度等级:common(60%)、uncommon(25%)、rare(10%)、epic(4%)、legendary(1%)
  • 外观自定义:6 种眼睛样式、8 种帽子(common 不戴帽)、1% 概率闪光(shiny)
  • 属性系统:DEBUGGING、PATIENCE、CHAOS、WISDOM、SNARK,每个宠物有峰值属性和谷值属性
  • AI 生成灵魂:名字和性格由 AI 在首次孵化时生成,作为"灵魂"持久化存储

2. 文件结构

src/buddy/
├── types.ts              # 类型定义(物种、稀有度、属性等)
├── companion.ts          # 核心生成逻辑(PRNG、确定性孵化)
├── sprites.ts            # ASCII 精灵渲染(每物种 3 帧动画 + 帽子叠加)
├── CompanionSprite.tsx   # React 组件(精灵动画、气泡、爱心特效)
├── useBuddyNotification.tsx  # 启动提示(彩虹 /buddy 广告)
└── prompt.ts             # 系统提示词注入(让 AI 不冒充宠物)

3. 确定性生成原理

这是整个系统最核心的设计——同用户永远孵出同一只宠物

3.1 用户 ID 的来源

// companion.ts
export function companionUserId(): string {
  const config = getGlobalConfig()
  return config.oauthAccount?.accountUuid ?? config.userID ?? 'anon'
}

优先级:

来源 场景 稳定性
oauthAccount.accountUuid 已登录 OAuth 账号 跨设备不变
userID 未登录,本地随机生成 本机不变
'anon' 都没有(极端情况) 每次不同

未登录时,userID 在首次使用时由 crypto.randomBytes(32).toString('hex') 生成一个 64 字符的十六进制字符串,保存在 ~/.claude.json 中:

// config.ts
export function getOrCreateUserID(): string {
  const config = getGlobalConfig()
  if (config.userID) return config.userID
  const userID = randomBytes(32).toString('hex')
  saveGlobalConfig(current => ({ ...current, userID }))
  return userID
}

3.2 种子化 PRNG

宠物属性由确定性伪随机数生成器(PRNG)决定:

用户ID + 固定盐值 "friend-2026-401"
    ↓ hashString()
    ↓ mulberry32()
    ↓ 确定性随机序列
    ↓ 依次抽取:稀有度 → 物种 → 眼睛 → 帽子 → 闪光 → 属性

关键实现:

// Mulberry32 — 轻量级种子化 PRNG
function mulberry32(seed: number): () => 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
  }
}

hashString 将用户 ID 字符串转换为数字种子,支持 Bun 原生哈希和 FNV-1a 回退两种实现。

3.3 完整生成流程

rollFrom() 函数按固定顺序依次从 PRNG 序列中抽取所有属性:

function rollFrom(rng: () => number): Roll {
  const rarity = rollRarity(rng)          // 1. 稀有度
  const bones: CompanionBones = {
    rarity,
    species: pick(rng, SPECIES),          // 2. 物种(18选1)
    eye: pick(rng, EYES),                 // 3. 眼睛(6选1)
    hat: rarity === 'common'              // 4. 帽子
      ? 'none'                            //    common 强制无帽
      : pick(rng, HATS),                  //    其他稀有度 8选1
    shiny: rng() < 0.01,                  // 5. 闪光(1%)
    stats: rollStats(rng, rarity),        // 6. 属性
  }
  return { bones, inspirationSeed: Math.floor(rng() * 1e9) }  // 7. AI种子
}

3.4 稀有度抽卡

稀有度通过加权随机轮盘抽取:

const RARITY_WEIGHTS = {
  common: 60,    // 60%
  uncommon: 25,  // 25%
  rare: 10,      // 10%
  epic: 4,       // 4%
  legendary: 1,  // 1%
}

实现原理:累加权重,用随机数落点确定稀有度。总权重 100,随机数 [0, 100) 从高到低逐级扣除权重,落为负数时返回对应等级。

3.5 属性随机算法

5 个属性:DEBUGGINGPATIENCECHAOSWISDOMSNARK

每个稀有度设定一个属性下限(floor)

稀有度 floor 影响
common 5 所有属性的基础值极低
uncommon 15 有一定基础
rare 25 中等偏上
epic 35 较高基础
legendary 50 所有属性都不低

生成过程分三步:

步骤一:随机选定 Peak(巅峰属性)和 Dump(废物属性)

从 5 个属性中随机选 1 个 Peak,再随机选 1 个 Dump(确保不与 Peak 重复)。

步骤二:按公式计算各属性值

// 峰值属性
stats[peak] = Math.min(100, floor + 50 + Math.floor(rng() * 30))
// 范围:floor+50 ~ floor+79,上限截断到 100

// 废物属性
stats[dump] = Math.max(1, floor - 10 + Math.floor(rng() * 15))
// 范围:floor-10 ~ floor+4,下限保护到 1

// 其余 3 个普通属性
stats[name] = floor + Math.floor(rng() * 40)
// 范围:floor ~ floor+39

步骤三:各稀有度属性区间一览

稀有度 Peak 范围 Dump 范围 普通属性范围
common 55~84 1~19 5~44
uncommon 65~94 5~29 15~54
rare 75~100 15~39 25~64
epic 85~100 25~49 35~74
legendary 100 40~54 50~89

注:legendary 的 Peak 理论为 100~129,但被 Math.min(100, ...) 截断为固定 100。

3.6 外观与特殊属性

物种(18 种):均匀随机,pick(rng, SPECIES)

眼睛(6 种)· × @ °,均匀随机。

帽子(8 种):none / crown / tophat / propeller / halo / wizard / beanie / tinyduck。

  • common 稀有度强制无帽(hat: 'none'
  • 其他稀有度从全部 8 种中均匀随机

闪光(Shiny)rng() < 0.011% 概率触发,纯视觉特效标识。

灵感种子Math.floor(rng() * 1e9),用于后续 AI 生成名字和性格时提供确定性随机性。

4. 数据存储模型

宠物的属性被拆分为两部分:

4.1 Bones(骨骼)— 不持久化

type CompanionBones = {
  rarity: Rarity      // 稀有度
  species: Species    // 物种
  eye: Eye           // 眼睛样式
  hat: Hat           // 帽子
  shiny: boolean     // 是否闪光
  stats: Record<StatName, number>  // 属性值
}

Bones 每次从用户 ID 重新计算,不写入配置文件。这样设计的原因:

  1. 物种重命名不会破坏已有宠物
  2. 用户无法通过编辑配置伪造稀有度
  3. PRNG 参数调整会自动生效

4.2 Soul(灵魂)— 持久化

type CompanionSoul = {
  name: string         // AI 生成的名字
  personality: string  // AI 生成的性格描述
}
type StoredCompanion = CompanionSoul & { hatchedAt: number }

存储在 ~/.claude.jsoncompanion 字段中:

{
  "companion": {
    "name": "Crumpet",
    "personality": "A common cat of few words.",
    "hatchedAt": 1775005790230
  }
}

4.3 完整宠物对象

运行时通过合并得到完整宠物:

export function getCompanion(): Companion | undefined {
  const stored = getGlobalConfig().companion  // 从配置读 soul
  if (!stored) return undefined
  const { bones } = roll(companionUserId())   // 从 ID 算 bones
  return { ...stored, ...bones }              // bones 覆盖,保证一致性
}

5. 渲染系统

5.1 ASCII 精灵

每个物种有 3 帧动画用于闲置状态:

  • 帧 0:静止
  • 帧 1:轻微晃动
  • 帧 2:特殊效果(喷火、冒泡、天线等)

精灵高度 5 行、宽度 12 字符,{E} 占位符在运行时替换为实际眼睛样式。

帽子系统通过替换第 0 行实现(仅在该行为空时叠加),可选:crown、tophat、propeller、halo、wizard、beanie、tinyduck。

5.2 动画序列

500ms tick 驱动的闲置序列:

const IDLE_SEQUENCE = [0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 2, 0, 0, 0]
// 0=静止, 1=晃动, -1=眨眼, 2=特效

说话或被 pet 时进入兴奋状态,快速循环所有帧。

5.3 自适应布局

终端宽度 渲染模式
≥ 100 列 完整精灵 + 气泡(非全屏时内联,全屏时浮动叠加)
< 100 列 单行表情如 (✦ω✦) Crumpet,说话时替换为截断气泡文本

5.4 气泡机制

  • 说话后显示约 10 秒(20 个 tick)
  • 最后 3 秒进入淡出状态(颜色变灰)
  • 全屏模式下气泡浮动在 scrollback 上方
  • 非全屏模式气泡内联在精灵左侧(输入框会相应缩窄)

6. 系统提示词

当宠物存在时,系统会注入额外的提示词:

A small {species} named {name} sits beside the user's input box and
occasionally comments in a speech bubble. You're not {name} — it's
a separate watcher.

When the user addresses {name} directly (by name), its bubble will
answer. Your job in that moment is to stay out of the way: respond
in ONE line or less, or just answer any part of the message meant
for you.

这确保 AI 不会冒充宠物,在用户直接叫宠物名字时让出舞台。

7. 时限与特性开关

7.1 触发窗口

// 提示窗口:2026 年 4 月 1-7 日(本地时间,非 UTC)
function isBuddyTeaserWindow(): boolean {
  const d = new Date()
  return d.getFullYear() === 2026 && d.getMonth() === 3 && d.getDate() <= 7
}

// 命令存活:2026 年 4 月起永久可用
function isBuddyLive(): boolean {
  const d = new Date()
  return d.getFullYear() > 2026 || (d.getFullYear() === 2026 && d.getMonth() >= 3)
}

使用本地时间而非 UTC,使得"彩虹 /buddy"提示在全球 24 小时滚动出现,避免单一时间点的流量尖峰。

7.2 特性开关

整个系统受 feature('BUDDY') 门控,可在构建层面完全关闭。

8. 反作弊设计

系统在多处防止用户伪造稀有宠物:

攻击向量 防御措施
编辑配置中的稀有度 Bones 不持久化,每次从 ID 重算
伪造 species/eye/hat 同上
修改 userID 需要同时重新孵化,但新 ID 可能孵出更差的宠物
物种名冲突 String.fromCharCode 编码物种名,绕过构建系统的代码名检查

9. 总结

Buddy 系统是一个精心设计的彩蛋功能,核心理念是确定性生成 + 属性/灵魂分离

  • 生成流程userId + salt → hash → PRNG → 稀有度(加权) → 物种(均匀) → 眼睛(均匀) → 帽子(common=none) → 闪光(1%) → 属性(peak/dump/normal) → 灵感种子
  • 稀有度:60/25/10/4/1 加权抽卡,决定帽子有无和属性下限(common=5, legendary=50)
  • 属性:1 个 peak(floor+50~79)、1 个 dump(floor-10~+4)、3 个 normal(floor~+39),legendary peak 固定 100
  • 外观:18 物种 × 6 眼睛 × 8 帽子(common 除外)× 1% shiny,纯确定性随机
  • 外属性(Bones) 完全由用户 ID 决定,不持久化,不可篡改、不可重抽
  • 灵魂(Soul) 由 AI 一次性生成,持久存储在 ~/.claude.json
  • 渲染 根据终端宽度自适应,窄终端降级为单行表情
  • 交互 通过气泡评论、pet 爱心、闲置动画增添趣味性

这种设计既保证了公平性(稀有度与用户 ID 绑定,属性由算法确定),又留出了个性化的空间(AI 生成的名字和性格),同时通过不持久化 Bones 的机制杜绝了配置篡改。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐