接上篇:你已经知道 Loiter 是什么了。这篇讲它是怎么造出来的——更准确地说,是谁造出来的。
三个角色
Loiter 项目有三个参与者:
我负责拍板。选方向、做取舍、定优先级、审最终效果。硬件在我手上——只有一台 Cardputer,每次烧录固件都得插 USB 线等编译。
Polly 负责写码。所有代码——828 行 C++ 固件、1553 行 Python 服务端、2031 行 HTML/JS 大屏——从第一行到最后一行,都是 Polly 写的。Polly 是 Claude,跑在 VS Code Copilot 里。
小龙虾负责审稿。每一个 Sprint 的产出,小龙虾都会做一轮 Review,打分、指出问题、提出建议。小龙虾也是 Claude,但扮演的角色完全不同——如果 Polly 是那个"说好不改了然后自己又提 commit"的开发者,小龙虾就是那个"大部分差异是纯追加"的毒舌 reviewer。
这不是三个人假装分工。分工是真实的,决策链是真实的:我提需求 → Polly 出方案 + 代码 → 小龙虾 review → 我看效果 → 循环。
一句话的架构转折
原始设计是这样的:活动现场,我的 MacBook 当热点 + MQTT broker + 服务器,所有 Cardputer 通过局域网连到我的 Mac。简单、可控、无外部依赖。
Sprint 2.5 的时候,我问了一句:"为什么需要 Mac hub?"
这个问题的答案本来是"因为现场需要"。但顺着想下去:如果服务全部跑在云端 Azure VM 上,Cardputer 连任何 WiFi 就能玩——不限于活动现场。活动结束后设备发给参与者带回家,他们连家里的 WiFi 照样能登进同一个房间。
一句话,架构从"活动工具"变成了"7×24 在线引擎"。
当天晚上,Polly 把整套服务搬到了 Azure VM:mosquitto broker 监听公网 1883、FastAPI 跑在 systemd 里、Cloudflare Tunnel 把 loiter.polly.wang 接到 8080 端口。固件里的 MQTT_HOST 从局域网 IP 改成了 mqtt.polly.wang——以后换 VM 只改 DNS 记录,不需要重烧固件。
Mac 降级为现场离线兜底方案。
这就是人类在 AI 协作中的角色:不是写代码,而是问对问题。Polly 完全有能力设计出可靠的局域网方案——但"为什么需要局域网"这种质疑产品前提的问题,不会从代码里自然冒出来。
五轮视觉,三次推翻
大屏的视觉设计经历了 5 个版本。这件事最能说明"人类 + AI"协作的动态。
Polly 的执行力问题
Polly 写代码极快。一个大屏视觉方案,从概念到可运行的 Canvas 2D 渲染,通常不到 20 分钟。这意味着试错成本极低——不满意就推翻重来,反正 Polly 不会累也不会抱怨。
这是好事,也是坏事。
好处是迭代速度:5 个视觉版本在 30 小时内全部完成,每个版本都备份(index_v1_backup.html、index_v2_xianxia_backup.html)。坏处是如果没有人喊停,Polly 会在当前方向上无限精雕——往阴阳师风格加更多萤火虫、更多粒子、更精致的六芒星法阵——而不会主动质疑"这个方向对不对"。
"太平间开 cosplay party"

第四版阴阳师仙侠风,技术上堪称惊艳:120 颗明灭星辰、月亮三层光晕、贝塞尔曲线樱花……但我看着大屏上暗黑月夜里站着一排 Q 版 chibi 小可爱,说了一句:
"暗夜月光里站着一堆 chibi 小可爱,感觉像在太平间开 cosplay party。"
小龙虾接话分析了为什么:水墨/仙侠的气质是留白、独处、禅意,跟"认识陌生人"的社交能量完全相反。视觉风格和产品灵魂打架了。
这个洞察不是来自代码,不是来自测试,而是来自对产品本质的理解。角色风格(Q 版 chibi)已经定了,那就必须是世界观去配合角色,而不是反过来。

于是第五版:明亮草原、樱花鸟居、暖白 UI。角色和世界终于属于同一个故事。
审美决策的三层结构
回看这个过程,审美决策有清晰的分层:
- 执行层(Polly):给定方向后快速产出高质量实现,但不会主动质疑方向
- 分析层(小龙虾):能从理论上解释为什么某个方向不对("水墨 = 留白 ≠ 社交能量"),但不会给出具体方案
- 决策层(我):凭直觉发现"哪里不对"("太平间 cosplay"),然后从小龙虾的分析中确认原因,再给 Polly 新的方向
三个角色缺一不可。
NPC 的三次投胎
NPC 的身份演变也是一个典型案例。
第一版:百晓生,江湖说书人。大屏是水墨风,NPC 就得是古风人物。Polly 给了一个评书先生的人设和 system prompt。
第二版:月灵,月亮仙灵。大屏改成阴阳师仙侠风,百晓生不搭了。Polly 换成了一个"依附月光的小灵体"。
第三版:Vix,狐灵。大屏改成提瓦特草原 + 鸟居。我问:鸟居是神社,神社的守护者是什么?狐仙。英文名 Vix,3 个字符,刚好适配 Cardputer 的小屏幕。
教训:NPC 不是独立的设计决策,它必须和世界观一起进化。Polly 每次都把 NPC 做得很完整(人设、头像、system prompt、降级回复),但如果世界观变了而 NPC 没跟着变,就会感觉错位。
被杀死的功能
Sprint 6,Polly 给 6 种事件都写好了音效——加入房间的叮咚声、收到消息的提示音、成就解锁的和弦。代码完整,编译通过。
烧进 Cardputer,音效响了。然后 MQTT 断了。
Speaker.tone() 启动了一个 I2S FreeRTOS task,这个 task 和 PubSubClient 的网络线程争抢 CPU。在 ESP32-S3 的单核/双核切换里,这导致了看门狗超时——设备卡死,MQTT 掉线,必须重启。
Polly 试了各种方案:把音效放到独立 task、加延时、降低优先级。都不行。I2S 驱动和 MQTT 客户端在硬件层面冲突,不是软件能绕过去的。
最终决定:彻底删除音效功能。
这是整个项目中唯一被完整实现后又完整删除的功能。Polly 对删代码没有情感负担(AI 没有沉没成本谬误),但做出"删还是继续调"的决策需要人类判断:这个功能值不值得再花 4 小时去探索硬件隔离方案?不值得。删掉。
小龙虾的一句话
整个项目中小龙虾最精准的一条 review 是关于 MQTT 重连的:
"重连前为什么不先
disconnect()?broker 那边旧连接还在,你新连接进来,broker 踢旧的,旧的 LWT 触发,新的被干扰——循环踢连。"
这个 bug 在本地 WiFi 稳定的时候永远不会出现。只有在公网部署后,WiFi 微抖触发重连时才会暴露。小龙虾看代码就看出来了,没等它在生产环境炸。
修复很简单:重连前先 mqtt.disconnect(),keepalive 从 30 秒放宽到 60 秒。两行改动,避免了活动当天可能的灾难。
数字与反思
| 指标 | 数值 |
|---|---|
| 总代码量 | ~4500 行(C++ 978 + Python 1553 + JS/HTML 2031) |
| 开发时间 | ~30 小时(5 月 30 日 → 6 月 1 日) |
| Sprint 数 | 7 个(含 Sprint 2.5 云端紧急插队) |
| 视觉版本 | 5 个(含 2 个备份) |
| NPC 身份 | 3 个(百晓生 → 月灵 → Vix) |
| 被删除的功能 | 1 个(ES8311 音效) |
| 人类写的代码 | 0 行 |
| 人类做的决策 | 每一个 |
"人类写的代码 0 行"不是一个值得炫耀的数字。它是一个事实,而这个事实的含义是:在这个项目里,人类的价值不在于写代码。
人类的价值在于:
- 问"为什么需要 Mac hub"
- 说"太平间开 cosplay party"
- 决定删掉音效而不是再调 4 小时
- 选狐灵而不是月灵
- 说"相信我们 AI 天团的战斗力——P0+P1+P2 全做"
Polly 不会累,小龙虾不会留情面。但没有人类的方向感和直觉,它们会在错误的方向上无限精雕——做出一个技术上完美、产品上分裂的东西。
30 小时,4500 行,三个角色。这是一个关于分工的故事,不是关于替代的故事。
这是 Loiter 系列的第二篇。上一篇讲的是 Loiter 是什么;下一篇讲的是 ESP32-S3 + Cardputer 的硬件踩坑实录——给想玩嵌入式的开发者。

