
马鞍
马鞍:一款用于实时编辑的数据包调试器
查看大图马鞍
马鞍:一个用于实时编辑的数据包调试器
一款 Fabric 模组,在 Minecraft 中嵌入了调试适配器协议服务器,使得数据包 .mcfunction 文件可以从 VS Code(或任何 DAP 客户端)进行调试——支持断点、单步执行、时间回溯,以及在函数运行时所处游戏状态的实时检查与编辑。
在 Minecraft 中,马鞍能让你驾驭原本自行其是的生物。这个模组则为数据包的执行装上了马鞍:在你想要的地方停下它(断点),让它后退(时间回溯),以及引导它(实时编辑)——而不是看着它狂奔而过。
- Minecraft 26.2 / Fabric Loader ≥ 0.19.0 / Fabric API
- Java 25(26.x 版本是未混淆的;构建脚本使用了无重映射的
net.fabricmc.fabric-loom插件) - DAP 端点:
127.0.0.1:16352——可通过-Dsaddle.host/-Dsaddle.port覆盖。除非设置了-Dsaddle.allowRemote=true,否则拒绝非回环地址的绑定:调试端口允许未经身份验证的命令执行,因此只在受信任或隧道网络上暴露它。
快速开始
安装模组(以及 Fabric API)并启动一个世界或服务器。
从 Marketplace 安装 VS Code 扩展 Saddle — Minecraft Datapack Debugger(或在扩展视图中搜索 "Saddle")。如果要从源码构建,请执行:
cd vscode-extension npx --yes @vscode/vsce package --allow-missing-repository code --install-extension saddle-debug-*.vsix在 VS Code 中打开你的数据包文件夹,在
.mcfunction文件中设置断点,然后运行 Attach to Minecraft (Saddle)(或按 F5)。调试控制台和游戏聊天都会告知你附加到了哪个世界。在游戏中触发一个函数(通过 tick 函数或直接在调试控制台中触发)。
详细的编辑器端功能导览,请参阅 vscode-extension/README.md。
工作原理
- 一个混入到
CommandFunction.fromLines中的代码,会将每个解析后的命令入口包裹在一个带有其(函数 ID, 源码行)来源的装饰器中,并记录函数源码文本。宏($...)行在解析时被标记,并在MacroFunction实例化它们时被包裹,因此它们也是可调试的。当没有调试客户端附加时,每条命令的运行时开销仅为一次易变读取。 - 在命中断点/单步执行/暂停时,服务器线程会在命令执行内部暂停——游戏在保留原版执行状态的情况下在函数中间冻结。暂停期间,线程会处理一个任务队列,因此需要游戏状态(变量、求值、内省)的 DAP 请求仍然可以在服务器线程上安全运行。
- 断点路径会根据文件路径中的
data/<命名空间>/function/<路径>.mcfunction段映射到函数 ID,因此任何工作区布局都可以无需配置地工作。在注释行或空行上设置的断点会向下移动到下一个可执行行。
DAP 支持
| 区域 | 请求 |
|---|---|
| 生命周期 | initialize,launch/attach,configurationDone,disconnect |
| 断点 | setBreakpoints,breakpointLocations——支持普通命令和宏行;注释/空行会移动到下一个可执行行 |
| 执行 | continue,next,stepIn,stepOut,pause |
| 状态 | threads,stackTrace,scopes,variables,setVariable,source |
| 控制台 | evaluate——执行任意命令,异步响应,因此命中断点的命令不会延迟堆栈视图;暂停时,它在隔离的 ExecutionContext 中执行。completions 提供 Brigadier 建议,因此调试控制台可以像游戏内聊天一样自动补全 |
| 悬停 | evaluate(context: hover) 无需执行命令即可解析宏参数($(name))、实体选择器(@e[...])和坐标三元组(~ ~2 ~) |
| 输出 | 游戏内聊天内容会作为 output 事件镜像到客户端:系统广播(/say、死亡、加入)、玩家聊天以及针对单个玩家的消息(/tellraw、/msg) |
| 时间回溯 | stepBack / reverseContinue 导航已执行命令的记录(环形缓冲区,-Dsaddle.ttd.steps,默认 20000)。在回溯时,会显示该时刻的堆栈、执行器、宏参数以及重建的记分板/存储状态——作用域会保留其实时名称,因此在当前和历史之间移动时,展开的行不会丢失,执行器作用域会带有一个 (time travel) 标记。向前单步会重播记录直到回到当前;continue 越过记录后会恢复实时执行。saddle/trace 返回最近的执行跟踪记录 |
变量(“寄存器与内存”)
每个栈帧在停止时都会暴露实时作用域,所有作用域都读取游戏状态:
- 执行器 — 命令来源摘要(执行者、位置、旋转、维度)以及执行实体可编辑的 NBT 树的惰性展开。
- 宏参数 — 当前宏帧的
$(...)值。 - 命令 — 即将运行的命令,每个实体选择器都解析为它当前匹配的实体(每个实体都可展开为实时 NBT),每个坐标三元组都解析为它所指向的方块。
- 监视 — 用户固定的表达式(见下文),每次请求时都会重新实时解析。
- 记分板 — 每个记分项及其分数;分数值可通过
setVariable编辑。 - 存储 — 每个命令存储 ID 作为一个可编辑的 NBT 树;叶节点值通过
setVariable接受 SNBT。容器预览基于大小({400 条目}),因此浏览大型存储仍然很快;编辑会触发一个invalidated事件,以便客户端重新获取过时的行。
在 .mcfunction 文件中暂停时,悬停在 $(name)、@e[...] 或 1 2 3/~ ~2 ~ 上会内联显示相同的数据(通过 VS Code 扩展)。
监视与固定表达式
VS Code 的 WATCH 面板、固定的“监视”作用域(saddle/pin、saddle/unpin、saddle/pins)以及 Saddle Watch 视图都接受:
@e[type=pig]— 匹配的实体,可展开为实时 NBTstorage <id> [path]/entity <uuid> [path]/block <x> <y> <z> [path]— 目标处的实时(可编辑)NBTscore <objective> [holder]— 单个分数,或省略持有者时显示整个记分项(可编辑)scoreboard/storage— 所有记分项 / 所有存储 ID$(name)— 选定帧的宏参数;单独的坐标三元组解析为它们指向的方块
Saddle Watch(实时 + 可编辑,无需断点)
该扩展在“运行和调试”侧边栏中添加了一个 Saddle Watch 视图——一个集成了 WATCH 和 Variables 功能的监视面板,无需断点:
- 实时:固定的表达式通过一个计时器(
saddle.liveWatchRefreshInterval,默认 1 秒)刷新,通过无状态的saddle/live {expression, path}请求实现,该请求无论游戏是在运行还是暂停,都会在服务器线程上读取游戏状态——记分板实时增加,实体位置移动,存储随函数写入而更新。 - 可编辑:由分数或 NBT 支持的行会显示一个内联铅笔图标;编辑通过
saddle/liveSet {expression, path, name, value}进行并立即应用到实时游戏。 - 原生样式:该视图使用 VS Code 自身的调试令牌主题渲染(等宽行,名称、数字、字符串使用
debugTokenExpression.*颜色),因此看起来与 Variables/WATCH 面板完全相同。
为了只保留一个监视面板,可以隐藏内置面板:右键单击“运行和调试”侧边栏中的任意节标题,然后取消勾选 Watch——VS Code 会记住。(扩展无法移除或替换内置视图,并且内置的 WATCH 仅在调试器停止时重新求值;这两者都是平台限制。将 Saddle Watch 拖到 WATCH 原来的位置,布局也会固定。)
自定义请求
minecraft/getScoreboard,minecraft/setScore {objective, holder, value}minecraft/getStorage {id?, path?},minecraft/listEntities {selector},minecraft/getEntity {uuid}(包含 NBT)minecraft/getData/minecraft/setData{type: storage|entity|block, target, path, value}— 原版/data风格的访问;方块目标为"x y z [dimension]"minecraft/getBlock {pos, dimension?}— 方块状态加上方块实体 NBT
测试
# 仅首次:接受 EULA 并禁用 tick 看门狗
mkdir -p run
printf 'eula=true\n' > run/eula.txt
printf 'max-tick-time=-1\nonline-mode=false\npause-when-empty-seconds=0\n' > run/server.properties
./gradlew runServer # 终端 1
python3 scripts/dap_smoke_test.py # 终端 2
该脚本会将 scripts/test-datapack 安装到世界中,执行 /reload,并端到端地演练完整的调试循环(108 项检查):断点、单步执行、时间回溯(后退/反向继续/历史状态重建)、宏断点和宏参数值、注释行偏移、实时变量读/写(记分板、存储 NBT、实体 NBT)、选择器/坐标解析、悬停求值、控制台补全、聊天输出镜像、实体/方块数据请求、求值与暂停。CI 在真实的专用服务器上运行相同的测试套件,推送 v* 标签会将 jar 和 vsix 发布为 GitHub Release。
注意事项
- 保持模组和 VS Code 扩展的版本匹配:模组在附加时会发送一个
saddle/version事件,当两者版本(主版本.次版本)不一致时扩展会发出警告。 - 在专用服务器上设置
max-tick-time=-1:在断点处暂停会挂起服务器线程,否则会触发看门狗。 - 在单人游戏中,如果游戏长时间保持暂停状态,内部客户端可能会断开连接。
- 单步执行超过所有排队的命令的末尾后,会话将保持运行状态,直到下一次命中断点/暂停(例如,下一个 tick 函数命令)。
故障排除
- 编辑似乎未生效 /
data get显示旧值。 首先检查附加公告:附加时,Saddle 会在调试控制台中打印Saddle: debugger attached to world '…',并在游戏聊天中打印[Saddle] Debugger attached。如果聊天消息没有出现在你的世界中,说明另一个拥有 Saddle 的 Minecraft 实例占用了端口(其日志会显示Failed to bind DAP server),并且你的编辑落在了那个实例中——关闭它或使用-Dsaddle.port来区分它们。另请注意,如果一个 tick 函数在每个 tick 都重写记分板/存储,那么在你恢复后,手动编辑会立即被覆盖。 Watched/Storage作用域仅编辑实时数据。在时间回溯时,同名的作用域显示记录的值且为只读——执行器作用域中的(time travel)行会告诉你正在查看哪种模式。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。