Buddy 电子宠物系统
18 个物种、5 级稀有度、RPG 属性、Mulberry32 确定性生成、ASCII 动画系统的源码级完整拆解。
Buddy 电子宠物系统 — 源码级完整拆解
在 Claude Code 的源码里,藏着一个完整的电子宠物系统。一个坐在终端输入框旁边的 ASCII 小动物,会对你的对话做出反应,偶尔冒出气泡说两句。有 18 个物种、5 级稀有度、RPG 属性、帽子系统,还能撸——撸了会飘爱心。它叫 Buddy,计划在 2026 年 4 月 1 日作为愚人节彩蛋上线。
整个系统由 src/buddy/ 目录下的 6 个文件组成,总代码量并不大,但设计精巧得令人惊叹。这里是对每一个细节的完整拆解。
整体架构:五层分工
| 文件 | 职责 | 关键机制 |
|---|---|---|
types.ts | 数据结构与常量 | 18 物种、5 稀有度、6 眼睛、8 帽子、5 属性的完整定义 |
companion.ts | 确定性生成算法 | Mulberry32 PRNG + FNV-1a 哈希 + 骨架/灵魂分离 |
sprites.ts | ASCII 渲染引擎 | 18×3=54 帧精灵画、帽子叠加、眼睛替换 |
CompanionSprite.tsx | React 动画组件 | 500ms tick、15 帧空闲序列、气泡、爱心 |
prompt.ts | AI 集成 | 「礼让协议」——告诉主 AI 不要扮演宠物 |
useBuddyNotification.tsx | 发现设计 | 4 月 1-7 日彩虹提示、/buddy 触发检测 |
核心设计思路:宠物的外观由你的用户 ID 决定(确定性生成),性格由 AI 取名(LLM 生成)。前者不存储,后者永久保存。
18 个物种
每种动物是一组 5 行高 × 12 字符宽的 ASCII 画,带 3 帧动画(静止、抖动 1、抖动 2)。完整列表:
Duck(鸭子)、Goose(鹅)、Blob(果冻怪)、Cat(猫)、Dragon(龙)、Octopus(章鱼)、Owl(猫头鹰)、Penguin(企鹅)、Turtle(乌龟)、Snail(蜗牛)、Ghost(幽灵)、Axolotl(六角恐龙)、Capybara(水豚)、Cactus(仙人掌)、Robot(机器人)、Rabbit(兔子)、Mushroom(蘑菇)、Chonk(胖橘)
Duck: (°> · Cat: =°ω°= · Dragon: <°~°>
Octopus: ~(°°)~ · Ghost: /°°\ · Robot: [°°]
Capybara: (°oo°) · Axolotl: }°.°{ · Chonk: (°.°)
String.fromCharCode() 编码。比如 capybara 写成 c(0x63,0x61,0x70,0x79,0x62,0x61,0x72,0x61)。原因是 capybara 是 Anthropic 内部的模型代号之一,直接写在代码里会触发构建时的安全扫描。为了统一风格,所有 18 个物种名都用了这种方式。写代码写到要给动物名字加密,也是头一回见。
五级稀有度系统
| 稀有度 | 概率 | 星级 | 颜色 | 属性下限 | 帽子 |
|---|---|---|---|---|---|
| Common(普通) | 60% | ★ | 灰色 (inactive) | 5 | 无 |
| Uncommon(稀有) | 25% | ★★ | 绿色 (success) | 15 | 随机 |
| Rare(珍稀) | 10% | ★★★ | 蓝色 (permission) | 25 | 随机 |
| Epic(史诗) | 4% | ★★★★ | 青色 (autoAccept) | 35 | 随机 |
| Legendary(传说) | 1% | ★★★★★ | 橙色 (warning) | 50 | 随机 |
稀有度通过权重轮盘算法决定:把所有权重加起来(60+25+10+4+1=100),生成一个 0-100 的随机数,依次减去每级的权重,第一个使结果变负的等级就是你的稀有度。
还有一个隐藏属性——Shiny(闪光),所有稀有度均为 1% 概率触发。Shiny + Legendary = 0.01%,万分之一的终极稀有组合。
核心设计:Bones/Soul 分离
这是整个系统最精巧的架构决策。一个宠物由两部分组成:
rarity— 稀有度species— 物种eye— 眼睛样式(6 种:·, ✦, ×, ◉, @, °)hat— 帽子(8 种:none, crown, tophat, propeller, halo, wizard, beanie, tinyduck)shiny— 是否闪光stats— 5 项 RPG 属性
从 hash(userId + salt) 确定性生成。每次启动 Claude Code 都重新算一遍,永远不写入磁盘。
name— 名字(如 "Noodle")personality— 性格描述hatchedAt— 孵化时间戳
孵化时由 LLM 一次性生成,永久存储在 ~/.claude/config.json 中。
1. 防作弊 — 你没法通过编辑配置文件把自己的宠物改成 Legendary,因为稀有度根本不存在于配置文件里。
2. 防损坏 — 开发者可以安全地重命名物种或调整数组顺序,不会破坏已有宠物。
3. 简化存储 — 只需要存两个字符串(名字和性格),其余全部按需重算。
确定性生成算法
整个生成过程基于一条链:userId → 哈希 → 种子 → PRNG → 所有属性。
Hash 函数
- Bun 运行时:使用原生
Bun.hash() - 其他运行时:FNV-1a 32 位哈希(初始值
2166136261,每个字符 XOR 后乘以16777619,最终掩码为 32 位无符号整数)
Salt 值
'friend-2026-401' — 拼接在 userId 后面再哈希,防止与其他系统的哈希结果碰撞。
Mulberry32 PRNG
源码注释写道:"tiny seeded PRNG, good enough for picking ducks"(微型种子 PRNG,选鸭子够用了)。魔法常数 0x6d2b79f5,输出一个确定性的 [0, 1) 浮点数。同一个 seed 永远产生同一个序列。
完整的 roll() 流程
缓存策略:单条目缓存 rollCache = { key, value }。因为这个函数会从 3 个热路径调用(500ms tick、按键、turn observer),但 userId 在会话内不变,所以只缓存一个条目就够了。
RPG 属性系统
每只宠物有 5 项属性,取名很有 Anthropic 的味道:
| 属性 | 含义 | 范围 |
|---|---|---|
| DEBUGGING | 调试能力 | 1-100 |
| PATIENCE | 耐心 | 1-100 |
| CHAOS | 混乱值 | 1-100 |
| WISDOM | 智慧 | 1-100 |
| SNARK | 毒舌值 | 1-100 |
属性生成机制
每只宠物有一个峰值属性(最高值)和一个垃圾属性(最低值),其余三个为散布属性(中间值)。稀有度决定所有属性的下限(floor):
视觉系统
8 种帽子的 ASCII 图案
帽子叠加在精灵的第 0 行(最顶部)。只有当该行为空白时才会叠加——如果精灵自带烟雾或触角等顶部装饰,帽子不会覆盖。如果所有 3 帧的第 0 行都是空白且没有帽子,该行会被裁掉(防止高度在动画中跳动)。
眼睛系统
6 种眼睛样式:· ✦ × ◉ @ °。精灵模板中使用 {E} 占位符,渲染时全部替换为抽到的眼睛字符。
动画系统
空闲动画循环
兴奋模式
当宠物在说话(有气泡)或被撸(/buddy pet)时,切换到快速动画模式——用 tick % frameCount 循环所有帧,产生快速抖动效果。
/buddy pet 爱心飘升
气泡与交互系统
| 参数 | 值 | 说明 |
|---|---|---|
| TICK_MS | 500ms | 基础动画间隔 |
| BUBBLE_SHOW | 20 ticks = 10 秒 | 气泡显示时长 |
| FADE_WINDOW | 6 ticks = 3 秒 | 气泡淡出时长 |
| PET_BURST_MS | 2500ms | 爱心飘升时长 |
终端响应式布局
- 宽终端(≥100 列):完整精灵(5行12字符)+ 侧边气泡(带圆角边框,34 字符宽)
- 窄终端(<100 列):单行 face 表情 + 简短引用文本
- 全屏模式:精灵正常渲染,气泡浮动到
bottomFloatslot(通过CompanionFloatingBubble组件),避免滚动区域裁切
companionReservedColumns() 函数计算精灵+气泡需要的列宽,PromptInput 据此调整输入框的换行位置。如果 Buddy 被静音、未孵化、或终端太窄,返回 0。
渐进式发现设计
4 月 1 日零点(你的本地时间),Claude Code 启动时会在状态栏闪现一个 15 秒的彩虹色 /buddy 提示。如果你好奇地输入了,就会进入孵化流程。4 月 7 日之后提示消失,但 /buddy 命令永久可用。
AI 的「礼让协议」
宠物和主 AI 是独立的两个实体。源码中注入给主 AI 的 prompt:
"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. Don't explain that you're not [NAME] — they know. Don't narrate what [NAME] might say — the bubble handles that."
这种「AI 之间的礼让协议」在 prompt 工程里可能是第一次见。只在第一次会话时注入(通过检查消息历史中是否已有 companion_intro 类型的 attachment),避免重复。
设计亮点总结
| 决策 | 机制 | 效果 |
|---|---|---|
| 确定性 + 不可伪造 | userId 哈希决定一切,配置文件里找不到可以改的数据 | 同一用户永远同一只宠物,且无法作弊 |
| 骨架/灵魂分离 | 计算的归计算,创造的归创造 | 代码可以安全更新物种列表而不破坏存量宠物 |
| 渐进式发现 | 彩虹提示 → 好奇输入 → 孵化惊喜 | 不强制不打扰,让用户自己发现彩蛋 |
| 不干扰工作 | 气泡 10 秒消失,可静音,窄终端自动简化 | 工具属性优先,宠物永远不会挡住工作 |
| 跨时区滚动发布 | 本地时区判定,24 小时滚动波 | 社交传播 + 负载平滑一箭双雕 |
| 防安全扫描编码 | String.fromCharCode 编码物种名 | 绕过 Anthropic 内部的代号扫描规则 |