Loiter 造厅记:30 小时 4500 行,一个人类和两个 AI

Loiter 造厅记:30 小时 4500 行,一个人类和两个 AI

接上篇:你已经知道 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"

第四版“阴阳师”风格的大屏——技术惊艳,但“太平间开 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 的硬件踩坑实录——给想玩嵌入式的开发者。

Comments