在开始做“手柄适配 Codex”之前,我们先做了一件看起来很基础、但实际上非常重要的事:制作一个手柄调试软件。
它的目标可以用一句非常直白的话说明:
我操作手柄,电脑上应该立刻看见反应。
按下一个按钮,页面上的对应位置点亮;推动摇杆,光点跟着移动;触摸触摸板,屏幕上出现触点;让手柄震动,页面显示正在执行,手柄真的反馈;转动手柄,电脑上的姿态模型发生变化。
这不是最终产品,却是后续所有工作的地基。因为在不了解硬件真实输入和输出之前,直接设计复杂的 Codex 交互,很容易把错误的假设带进整个系统。
更值得分享的是:这个软件不是按照传统方式先写完整需求、画完所有界面、再由工程师逐层实现的,而是通过 VibeCoding 逐步生成。我们不断告诉 AI 当前目标,运行它生成的版本,观察真实设备的反应,再把现象、错误和新发现反馈给 AI。软件就在这个过程中一层一层长出来。
一、先把目标说简单:让电脑看见手柄
一开始不需要说“我要做一个完整的 DualSense 设备抽象层”,也不需要一上来就设计复杂架构。对 VibeCoding 来说,一个好的起点是把目标说成用户能够感知的结果:
- 手柄连接后,电脑知道它来了。
- 按键按下和松开,电脑能显示状态变化。
- 摇杆和扳机的连续变化,电脑能显示数值或位置。
- 触摸板点击和滑动,电脑能显示触点。
- 手柄输出震动时,电脑能显示指令状态,手柄也产生真实反馈。
- 转动手柄时,电脑能显示运动数据和相对姿态。
这个目标有一个重要特点:每增加一种能力,用户都能立刻观察到结果。它把“硬件能不能工作”变成了一个可视化问题,也让 AI 生成的每一版代码都有明确的验收方式。
软件的数据链路大致是:
DualSense
→ pygame / SDL 读取设备事件与状态
→ Python 统一整理数据
→ Flask 提供状态接口和实时推送
→ 浏览器页面显示手柄状态
二、技术实现和产品设计是两条同时演进的线
如果只看代码,这个项目像是在不断增加功能:按键、触摸板、震动、六轴传感器、桌面封装。但从产品角度看,变化其实更有意思:我们一直在寻找一种更好的方式,让用户理解“手柄发生了什么”。
最开始,用户看到的是事件列表和数字;后来,用户看到的是中文状态卡片;再后来,用户看到的是一只和真实硬件位置对应的手柄;最后,触摸板、摇杆、扳机和姿态都被放回手柄本体中。产品界面从“调试日志”逐步变成“硬件的数字孪生”。
这两条线相互推动:技术每多读到一种硬件数据,产品就要思考怎样让它容易理解;产品每提出一种更自然的反馈方式,技术就要重新整理数据模型和事件链路。
系统架构:输入如何变成电脑上的反应
- DualSense 硬件
- pygame / SDL 主线程 · 事件泵与状态轮询
- Python 状态层 · 标准化按键、轴、触摸、运动数据
- Flask 本机服务 · 状态 API + SSE 实时推送
- 浏览器界面 · 手柄图、事件、数据和状态
- 震动测试请求
- 命令队列
- 低频 / 高频震动反馈
- 加速度计 / 陀螺仪读取
↻ 反馈进入下一轮
这里有一个关键的闭环:输入从手柄进入电脑,经过状态层和实时推送在页面上显示;当用户点击震动测试时,命令又从页面返回 Python,最终由 SDL 主线程控制手柄输出。所谓“让电脑有反应”,不是简单地把按钮编号打印出来,而是让硬件输入、软件状态、用户界面和硬件反馈形成闭环。
产品界面如何演进
- 早期版 · 状态面板 + 事件列表 · 先完成原理验证
- 第二版 · 中文分区 + 状态卡片 · 降低理解成本
- 第三版 · 手柄轮廓图 + 按键映射 · 所见即所得
- 第四版 · 触摸点整合进触摸板 · 输入回到硬件位置
- 第五版 · 六轴姿态 + 原生窗口 · 从网页实验走向应用
每一次界面变化都不是单纯的美化,而是一次产品假设的更新:
- 早期版的任务是证明“手柄输入可以被读取并实时显示”,所以状态面板、事件列表和原始编号已经足够。这个阶段甚至没有手柄图,用户只能通过文字、数字和模块状态判断设备发生了什么。
- 第二版开始考虑“用户能不能看懂”,于是加入中文名称、连接状态、设备信息和分区。
- 第三版由用户提供手柄示意图,界面不再把按钮当作一串编号,而是把按钮放到真实手柄对应的位置上。
- 第四版把触摸板轨迹从独立卡片整合回手柄图内部,用户在操作触摸板时,看到的就是手柄上触摸板区域的变化。
- 第五版增加相对姿态和 macOS 原生窗口,让软件从“浏览器里的调试页面”逐步接近“可以直接使用的硬件工具”。
这个演进过程说明:硬件调试软件的产品设计,不是先画一张漂亮的界面,再把数据塞进去;而是先用最简单的界面验证数据,再根据用户理解成本重新组织信息。
不同时期的真实界面
下面的截图不是示意图,而是从项目中保留下来的 macOS 应用版本实际启动后截取的画面。它们更适合用来观察“产品如何长出来”:同一个手柄,同一组底层数据,随着界面组织方式变化,用户理解硬件的成本也在变化。
v0.3.0:最早期的状态面板,甚至还没有手柄轮廓
这张图很重要,因为它说明了原理验证阶段的真实样子:我们并不是一开始就拥有现在这张完整的手柄界面,而是先用几个普通卡片回答一个问题——“电脑能不能读到手柄数据?”只有这个问题被验证后,才值得继续投入视觉化和产品化设计。
v0.6.0:功能已经出现,但问题仍然暴露在界面上
v0.6.2:手柄本体成为主要的信息入口
v0.7.0:从实验页面走向可交付应用
这四张图也提醒我们:截图不只是文章配图,它们本身就是灰度实验的证据。每次截取一个真实版本,都能帮助读者判断当时已经验证了什么、还暴露着什么问题。
三、从实现路径到设计演进:VibeCoding 如何推进每一轮变化
到这里,软件的实现路径已经清楚了:手柄输入进入 SDL,Python 整理状态,Flask 推送到页面,页面再把测试指令送回手柄。但产品并不是先把这条技术链路一次做完,再统一设计界面;我们是在每一轮 VibeCoding 中,同时推进“能不能读到”和“用户能不能看懂”两个问题。
每一轮的工作方式都很简单:先确定一个用户能直接感知的核心目标,再把目标用自然语言告诉 AI;AI 生成一个最小版本,我们运行它、操作真实手柄,最后根据看到的反馈决定下一轮怎么改变界面和实现。
因此,下面的重点不是罗列所有技术难点,而是观察每一轮如何用一句清楚的话推动产品往前走:从“先看到输入”,到“看懂手柄”,再到“验证反馈”,最后变成“可以交付的工具”。
四、VibeCoding 不是一句话生成完整软件
没有 VibeCoding 经验的人,最容易产生两个误解。
第一个误解是:只要把目标描述得足够长,AI 就能一次生成最终软件。实际上,硬件项目的很多问题只有运行之后才会暴露:系统事件字段可能和文档中的 C 结构体不同,设备热插拔可能不会自动刷新,平台线程模型可能限制库的使用方式,封装后还可能加载了另一份动态库。
第二个误解是:VibeCoding 等于不需要工程方法。恰恰相反,AI 写得越快,越需要人持续提供真实反馈、划定边界和要求验证。
这次软件制作采用的是一个很朴素的循环:
提出一个可观察的目标
→ 让 AI 实现最小版本
→ 启动软件并操作真实手柄
→ 记录页面现象、错误和日志
→ 让 AI 根据证据定位问题
→ 修改代码并增加回归测试
→ 再次运行验证
AI 负责快速生成代码、解释错误、提出修复和补测试;人负责连接真实设备、观察真实反馈、判断目标是否满足,以及决定下一步要解决哪个问题。
五、第一轮:先做最小 Web 监视器
本轮核心目标
用一句自然语言说,就是:“我操作手柄,浏览器应该马上显示对应的变化;先不追求漂亮界面,只要能稳定看到基础输入。”
第一轮只验证最基本的输入链路:Python 能否识别手柄,浏览器能否实时显示按键、摇杆、扳机、方向键和最近事件。
给 AI 的需求
这里有一个初学者很容易困惑的地方:为什么要指定 Python、pygame/SDL 和 Flask?这些名词如果没有接触过,单独看技术栈并不能帮助读者理解这个项目。
Python 是一种编程语言,语法相对直接,拥有大量可以调用硬件、处理数据和搭建小型服务的库。Flask 是一个轻量级的 Python Web 框架,可以让 Python 在本机启动一个小型 Web 服务,再由浏览器打开页面。pygame/SDL 则负责和手柄打交道:读取按钮、摇杆、扳机等输入。
这次选择 Python 和 Flask,不是因为它们对所有项目都最好,而是因为我过去实际使用过 Python,也用 Flask 做过小型网站。对 VibeCoding 来说,这个背景非常重要:如果 AI 生成的代码出现问题,我至少知道代码大概如何运行,也有能力在必要时手动排查和修改。最后实际调试过程中,绝大多数问题都由 AI 根据错误信息完成了修复,没有需要我亲自改代码;但“我有能力接管”让整个探索过程更有安全感。
因此,技术约束背后其实有一个可复用的原则:
优先选择自己能理解、能运行、必要时能接管的技术,而不是盲目追逐看起来最先进的技术。
原始提示词:目标有了,但信息还不完整
当时最初的需求可以还原成一段比较自然的表达:
帮我做一个 Python 程序,读取 PS5 手柄的按键、摇杆和扳机数据,
并在网页上显示出来。我希望能看到按下了什么按键、摇杆怎么动,最好能实时刷新。
先做一个能运行的版本。
这段话并没有错,而且足以让 AI 开始工作。但它把很多关键判断留给了 AI:页面要显示哪些状态?实时刷新采用什么方式?服务如何启动?没有手柄时是否应该正常运行?哪些功能第一轮不做?完成之后怎样判断“能运行”?
优化后的提示词:把验收条件说清楚
在实际协作中,我们把它整理成了更明确的版本:
请在当前项目中制作一个最小的 macOS 手柄输入监视器。
背景:我熟悉 Python,并使用过 Flask 做小型网站,因此本轮固定使用 Python、pygame/SDL
和 Flask。pygame/SDL 负责读取手柄,Flask 负责提供本机网页服务;请把手柄读取和 Web
服务分层,数据只在本机流转。
本轮目标:用户连接 DualSense 后,在浏览器页面实时看到设备连接状态、按钮按下/松开、
摇杆和扳机轴值、方向键状态以及最近事件时间线。
本轮暂不实现:触摸板、震动、自适应扳机、六轴姿态和复杂的游戏化界面。
完成后请提供:运行命令、测试命令、已知限制,以及无手柄环境下的启动验证。
请不要虚构真实手柄测试结果;如果某项能力尚未验证,请明确标注。
两段提示词的差别
左右滑动查看完整表格
| 维度 | 原始提示词 | 优化后的提示词 |
|---|---|---|
| 目标 | 读取手柄并显示 | 明确显示连接、按键、轴、方向键和事件时间线 |
| 技术选择 | 直接指定 Python | 说明选择 Python 的背景,并明确 pygame/SDL 与 Flask 的分工 |
| 项目范围 | “先做能运行的版本” | 明确本轮暂不做触摸板、震动、姿态和游戏化界面 |
| 验收方式 | “最好实时刷新” | 指定启动命令、测试命令和无手柄启动验证 |
| 事实边界 | 没有说明 | 要求 AI 区分已验证结果和未验证能力 |
| 架构要求 | 没有说明 | 要求手柄读取和 Web 服务分层,数据只在本机流转 |
优化后的提示词并不是单纯“写得更长”,而是补齐了 AI 最容易猜错的上下文。它告诉 AI 为什么选这些技术、这次只解决什么、明确不做什么,以及最终如何判断结果合格。
如何写出更高效的提示词
对比这两段提示词,可以总结出一个适合新手的顺序:
- 先说用户要看到什么:不要只说“读取数据”,要说按下按钮后页面应该出现什么反应。
- 再说你为什么选择某项技术:这能避免 AI 擅自更换技术,也让它理解哪些技术是项目约束,哪些只是建议。
- 限定本轮范围:把暂时不做的事情写出来,防止 AI 在第一轮就扩展到触摸板、震动、打包和复杂界面。
- 给出可验证的验收条件:运行命令、测试命令、无设备时的行为和真实设备验证方式都可以提前约定。
- 要求区分事实和猜测:尤其是硬件项目,不要让 AI 把“理论上支持”写成“已经验证”。
- 补充必要的工程边界:例如数据只在本机流转、读取线程与 Web 服务分层、不要覆盖已有文件。
提示词高效的关键,不是把所有细节一次写完,而是把“目标、背景、范围、边界、验收”这五件最影响结果的事情说清楚。至于具体代码结构,可以让 AI 在这些约束下提出方案,再通过运行结果继续调整。
第一轮的结果
最小版本完成了 Python 采集、Flask 状态接口、Server-Sent Events 实时推送和浏览器页面。无手柄时,服务能够启动并返回未连接状态;连接设备后,页面可以显示基础输入。
但很快出现了第一个真正的工程问题:软件启动时没有手柄,之后再连接手柄,页面仍然显示没有设备。
从截图证据看,第一阶段的界面重点仍然是把状态和异常暴露出来;此时不必急着追求最终视觉效果,先确保“设备状态可观察、失败原因可追踪”。
灰度反馈:让基础输入持续可见
灰度反馈目标
“即使手柄是在程序启动之后才连接,页面也应该自动发现它;拔掉、重新连接之后,状态仍然能够恢复。”
运行反馈
独立的 pygame 进程可以枚举到 DualSense,其他软件也可以读取它,但我们的 Web 监视器状态一直停留在启动时的“0 台设备”。这时最容易做出的错误判断是:蓝牙不稳定、手柄权限不够,或者页面刷新不及时。
反馈如何转成下一条自然语言需求
为了规避 macOS 下 pygame 初始化图形系统可能触发的 AppKit 主线程问题,第一版代码去掉了完整的 pygame.init()。但这样也没有正确处理 SDL 事件队列。程序没有持续泵送事件,设备数量、按键和轴状态就停留在旧缓存里。
换句话说,问题不在手柄,也不在浏览器,而在于“负责读取手柄的线程没有正确运行 SDL 的事件循环”。
我们没有直接要求 AI“把热插拔修好”,而是把证据完整告诉它:
现象:启动服务时没有连接手柄,之后连接 DualSense,API 仍显示 0 台设备;
但在另一个独立 pygame 进程中可以枚举到同一个设备。
请不要先猜蓝牙或权限问题。检查当前代码是否初始化了 SDL、是否持续泵送事件、
设备枚举和状态轮询是否运行在正确线程。请给出根因、最小修复方案和回归测试。
约束:macOS 下 pygame/SDL 的初始化和事件处理放在主线程,Flask 可以放在后台线程;
修复后要覆盖启动时无设备、运行中插入、断开和重新连接四种情况。
这一次 AI 的工作不是“凭经验改几行代码”,而是根据现象对照线程模型和事件循环定位原因。最终方案是:让 pygame/SDL 在主线程初始化、处理事件和轮询手柄状态,Flask 在后台线程提供 Web 服务,同时增加设备插入、断开和恢复逻辑。
这一轮带来的设计变化
硬件调试中,必须区分三个状态:
- 操作系统是否看见设备。
- 底层库是否读取到了设备。
- 你的应用是否正确更新并展示了设备状态。
只看到第三个状态异常,不能直接把问题归因于硬件。
六、第二轮:让更多硬件能力被看懂
本轮核心目标
“用户不应该只能看事件编号;按键、摇杆、扳机和触摸板应该在页面上形成和真实手柄相对应的反馈,操作结果应该回到真实硬件的位置上。”
给 AI 的自然语言任务
在基础输入已经稳定的前提下,继续增加 DualSense 的触摸板和可视化反馈。
界面要逐步从事件列表变成用户容易理解的手柄状态面板:按键能点亮,摇杆光点能移动,
L2/R2 能显示行程,触摸板能显示手指位置。
请保持已有按键和轴数据显示,不要为了增加新功能破坏当前输入链路;每增加一项能力,都提供
一个可以通过真实手柄观察的验收方式。
基础输入稳定之后,我们继续增加用户能够直接感知的输入能力。页面从简单的事件列表,逐步变成更直观的手柄状态面板:手柄轮廓上的按键会点亮,摇杆光点会移动,L2/R2 显示行程,触摸板显示手指位置。
触摸板:把底层事件翻译成用户反馈
触摸板是一次很典型的 VibeCoding 调试过程。
第一步,我们根据 SDL 的触摸事件概念实现了按下、移动和抬起处理,并在测试中构造了一个看似合理的事件对象。第二步,用户实际触摸 DualSense,真实事件确实到达了程序,但程序却因为 pygame.event.Event 没有 finger 字段而抛出异常,主循环退出,Web 服务也随之结束。
这里出现了一个很重要的差别:SDL 的 C 结构体字段叫 finger,但 pygame 转换到 Python 后的字段叫 finger_id,同时还有 touch_id、x、y 和 pressure。
如何用自然语言推动修正
我们把“程序在真实触摸时退出”的完整错误信息交给 AI,并要求它不要只修复字段,还要补充真实 pygame 事件回归测试:
真实 DualSense 触摸时,程序收到 Controller 触摸事件后退出:
AttributeError: pygame.event.Event 没有属性 finger。
请根据 pygame 2.6.1 的真实事件结构检查字段名称,不要直接照搬 SDL C 结构体字段。
请把触摸事件分发提取成可测试单元,使用真实 pygame.event.Event 构造方式增加按下、移动、
抬起测试;同时增加畸形触摸事件测试,确保可选触摸数据异常时不会终止普通按键和 Web 服务。
修复后,触摸处理统一使用 finger_id,事件分发被提取成可测试单元,异常触摸事件只更新错误状态,不再终止整个监视器。
后续版本的手柄示意图截图可以直观看到这一变化:触摸板不再是孤立的一行事件,而是被整合到真实手柄的位置中,用户可以把页面反馈和手上的动作直接对应起来。
触摸数据的产品语义也要一起修正
最初页面把触摸事件中的 pressure 当作连续压力值,还用它改变触点大小。但真实 DualSense 数据显示,接触时通常为 1,抬起时为 0,它表达的是“是否接触”,而不是手指按压强度。
于是我们又修正了界面语义:显示“已触摸/未触摸”、手指编号和坐标,不再把二值数据包装成压力传感器。
这个问题说明,数据字段有数值,不代表它的物理意义就是我们直觉中的意义。硬件项目需要用真实采样验证每个字段,而不是只看字段名称。
七、第三轮:让反馈真正回到手柄
本轮核心目标
“不仅要看到手柄输入,还要从电脑发出一条指令,让手柄真的震动,并且页面能告诉用户指令正在执行还是已经完成。”
给 AI 的自然语言任务
请把“电脑发出指令,手柄产生真实反馈”做成一个最小闭环。
页面提供低频、高频和双通道震动测试,并明确显示测试状态;Flask 请求线程不要直接操作
pygame Controller,请把指令交给负责 SDL 的主线程执行。完成后验证启动、自动停止、断开和
重复测试不会破坏输入监视。
如果只有浏览器显示,软件仍然只是一个输入监视器。为了验证“电脑发出指令后,硬件会反馈”,我们加入了震动测试。
页面提供低频、高频和自定义双通道震动测试,并显示“测试进行中”和“测试已完成”。后端不让 Flask 请求线程直接调用 pygame Controller,而是把震动命令放入队列,再由 SDL 主线程执行。这是因为 macOS 下跨线程操作手柄可能产生不稳定行为。
此时,调试软件完成了一个更完整的闭环:
手柄输入 → 电脑页面显示 → 页面发出测试指令 → 手柄产生震动反馈
这也是“让操作说明在电脑上看到反应”的具体含义:用户的动作和系统的回应都能被观察到。
八、第四轮:让运动数据变成可理解的姿态
本轮核心目标
“转动手柄时,页面应该显示方向变化;手柄静止时,当前姿态应该可以作为零点,不能让数字无意义地持续漂移。”
给 AI 的自然语言任务
请在现有手柄监视器中增加六轴运动显示和相对姿态。
先确认当前 pygame/SDL 能否取得 DualSense 的加速度计和陀螺仪数据;如果 Python 封装没有
直接接口,可以复用 pygame 自带的 SDL 动态库做最小 ctypes 桥接,不新增驱动。
页面要明确标注这是相对姿态,不要声称是绝对航向;启动、重连或点击校准后,以手柄当前静止
状态为零点,并通过滤波减少静止漂移。
先把硬件能力接进来
DualSense 有加速度计和陀螺仪,SDL 也提供传感器接口。但本机 pygame 2.6.1 的 Controller 类没有直接暴露传感器查询、启用和读取方法。
这时有三条路:换库、自己写底层绑定,或者放弃这项能力。我们先确认了 pygame 自带的 SDL 动态库版本,再采用 ctypes 桥接 SDL Controller Sensor API,复用已有 SDL 库,不额外安装驱动或 Python 依赖。
再把原始数据变成用户能理解的反馈
直接对陀螺仪角速度积分,短时间内可以得到姿态变化,但误差会不断积累。用户反馈静置时姿态已经慢慢偏离,需要让当前静止姿态自动成为零点。
修复方案不是简单把页面数字清零,而是增加了一个小型校准和滤波系统:
- 启动、重连或重新校准时采集一组静止样本。
- 估计陀螺仪偏置和初始重力方向,把当前状态设为零点。
- 俯仰和翻滚用陀螺仪保持短时响应,再用加速度计的重力方向纠偏。
- 静止时执行零角速度约束,并缓慢更新陀螺仪偏置。
- 偏航没有磁力计等绝对参考,因此只保证相对姿态,不声称是绝对航向。
在这里,VibeCoding 的重点是让 AI 逐步处理一个明确的反馈:不是“姿态功能不够高级”,而是“手柄静止时数字还在漂移”。具体的错误表现,比抽象的功能愿望更容易转化成有效修复。
九、第五轮:让工具从能运行变成可交付
本轮核心目标
“普通用户不应该先打开终端、启动 Python、再访问浏览器;双击一个 macOS 应用,就应该看到同样的硬件反馈,而且关闭窗口后后台服务也要一起结束。”
给 AI 的自然语言任务
请把当前 Flask 手柄监视器封装成一个可以双击打开的 macOS 应用。
原生窗口负责显示页面并管理后端进程,Flask 后端继续负责手柄采集和状态服务;应用启动时
自动等待健康检查并加载本机页面,关闭窗口时只结束自己启动的后端进程组并释放端口。
请保留当前已验证的按键、摇杆、触摸板、震动和六轴能力,并提供构建、签名和解压后实际运行验证。
当 Web 监视器功能逐渐完整后,使用方式仍然不够自然:用户要打开终端、激活虚拟环境、启动 Python,再打开浏览器。于是我们继续用 VibeCoding 解决“如何把它变成普通用户可以打开的 macOS 应用”。
让技术约束服务于交付体验
普通 Python WebView 方案会让 Python 同时承担 pygame、Flask 和窗口管理,而 pygame/SDL 在 macOS 上需要稳定的主线程模型。最终采用了分层架构:Objective-C/AppKit 负责原生窗口和 WKWebView,PyInstaller 打包 Flask 后端,原生窗口启动后端并等待健康检查,再加载本机随机端口。
用连续的自然语言反馈完成封装
这部分特别能体现“让 AI 根据错误继续迭代”的价值:
- 本机 Swift 编译器与 SDK 小版本不一致,原生外壳改为只依赖 Command Line Tools 的 Objective-C。
- 虚拟环境 Python 符号链接解析错误,调整打包入口和解释器处理。
- PyInstaller 缓存出现越界问题,改为在干净临时目录构建。
- 打包后的 pygame 使用
_internal/中的 SDL,传感器桥接必须优先加载同一份 SDL,否则会拿不到正确的 Controller 指针。 - 应用关闭时后端不响应 SIGTERM,需要由窗口只结束自己拥有的后端进程组,避免残留 Flask 端口。
- File Provider 自动添加 FinderInfo 破坏代码签名,构建和签名改在系统临时目录完成,再归档成 ZIP。
这些问题无法靠一次“请打包成 App”的提示词解决。有效做法是每次只解决一个可复现问题,并告诉 AI:错误发生在哪里、已经尝试了什么、什么行为必须保留、修复后如何验证。
最终,软件被封装为可以打开的 macOS 应用,应用窗口自动启动和关闭 Flask 后端,随机端口在关闭后释放,真实 DualSense 的按键、摇杆、触摸板、震动和六轴数据仍然可用。
v0.7.0 的截图是这一阶段的交付证据之一:它展示的不是代码目录,而是普通用户打开应用后能够看到的完整入口。文章中的版本截图因此和技术演进形成互相印证:v0.6.0 暴露问题,v0.6.2 证明关键能力逐步恢复,v0.7.0 证明这些能力被组织进了可交付的应用界面。
十、从这次项目看懂 VibeCoding 的实际工作方式
1. 先描述体验,再讨论技术
“让电脑看到手柄反应”比“实现一个基于 SDL 的事件抽象层”更适合作为第一句需求。体验目标能帮助 AI 选择合适的技术,也能让没有工程背景的人判断结果是否正确。
2. 每次只推进一个最小闭环
先有按钮和轴,再有触摸板,再有震动,再有运动传感器,最后才做应用封装。每一步都能启动、观察和回退,错误不会被一大堆新功能掩盖。
3. 把真实现象交给 AI,而不是只说“它不工作”
“启动后插入手柄仍显示 0 台设备”“触摸时出现 AttributeError”“姿态静置时持续漂移”“关闭 App 后端口仍被占用”,这些描述都比“热插拔有问题”“传感器不准”“打包失败”更有价值。
4. 让 AI 同时修代码和补测试
如果只改代码,不补测试,下次迭代很容易再次破坏旧功能。触摸事件问题就是一个例子:最初的测试构造了错误的字段,直到真实设备触发异常才暴露。之后测试改用真实 pygame 事件结构,并加入畸形事件回归。
5. 人负责判断,AI 负责加速
AI 可以解释 SDL 字段、改线程模型、写 ctypes 桥接、补测试和整理封装脚本,但它不能替代真实手柄,也不能替代用户对“这个反馈是否真的有意义”的判断。VibeCoding 的人机分工不是“人不写代码”,而是人把注意力放在目标、证据和取舍上。
十一、适合新手复刻的完整提示词
如果你也想制作一个硬件调试软件,可以从下面这段总提示词开始,然后在每次运行后追加真实观察结果:
我想为一个真实硬件制作调试软件,让用户操作硬件时,电脑上能实时看到输入状态,
并能触发硬件反馈。请使用 VibeCoding 的迭代方式协作,不要一次生成所有功能。
项目目标:<用一句话描述用户能看到的反应>
硬件:<设备名称、连接方式、已知输入和输出能力>
平台:<操作系统、版本、开发语言和可用库>
请按以下流程工作:
1. 先把目标拆成最小可观察闭环,并明确本轮不做什么。
2. 先检查项目目录、AGENTS.md 和项目日志,遵守已有规则。
3. 给出本轮实现计划、假设、风险和验证步骤。
4. 实现一个可以启动的最小版本,并提供自动测试。
5. 告诉我如何连接真实设备、如何操作设备、预期看到什么。
6. 我会把真实页面现象、错误日志和设备行为反馈给你。
7. 你必须基于证据定位问题,不能只猜测;修复时保留已有功能,并增加回归测试。
8. 每轮结束后更新项目日志,记录目标、行动、结论、未完成事项和下一步。
当前这一轮只实现:<填写一个最小能力,例如“显示按钮按下与松开”>。
运行之后,反馈可以这样写:
我按下了手柄的触摸板。页面先显示了一次触摸事件,随后程序退出。
这是完整错误日志:<粘贴错误>
请先解释根因,再给出最小修复;不要重写整个项目;修复后补一个能复现这个问题的测试,
并验证原有按钮和摇杆功能没有被破坏。
这两段提示词的关键,不是措辞多漂亮,而是把协作分成了目标、范围、观察、证据、修复和验证几个环节。只要持续把真实反馈放回循环,VibeCoding 就不再是“凭感觉让 AI 写代码”,而是一种适合快速探索软硬件问题的工程工作方式。
总结:先让硬件有反应,再让产品有意义
手柄调试软件的价值,不只是做出了一个页面或一个 macOS App。它帮助我们建立了可靠的硬件事实:什么输入可以读取,什么反馈可以控制,哪些数据容易误解,哪些能力受平台和库限制,哪些问题必须下沉到底层接口。
更重要的是,这个软件让我们亲自经历了一次完整的 VibeCoding:从一句简单目标开始,逐步生成最小版本;用真实设备发现问题;把现象和错误交给 AI;让 AI 修复并补测试;再通过实机观察确认结果;最后把能运行的原型封装成可交付的应用。
对于完全没有 VibeCoding 经验的人,可以把这次过程浓缩成一句话:
不要要求 AI 一次做完;让 AI 每次解决一个你能真实观察、真实验证的问题。
当电脑能够对手柄的每一次操作作出清晰反应时,我们才真正拥有了继续设计复杂 Codex 交互的基础。
