Proxyos Weekly 043
Laurence-042
- 2 minutes read - 407 wordsTL;DR 概览
第一章打磨完成,清除了多个强退死档风险,修复了关键 Bug;重构了任务与时间系统以提升体验。
本期目标
- 一个打磨完的 demo
进展速记
本期假设 / 预期
预期: 本期有 3 天,而第一章主要的问题无非就是 Terminal,所以我应该能至少搞定第一章。第二章最大的问题可能在网站架构调整和 demo 显示。
结果: 问题比想象得多,但我确实搞定了第一章。第二章最大的问题确实是网站架构调整,我需要额外补充一些网页来让茶馆的页面适当增多,而且解锁次序也得寻思下,并添加相关的提示。
本期确定性变化
新增:
- 将第一章中玩家在修复脚本里起的用户名写入存档,供后续章节使用
变更:
- 调整第一章 controller 的文件位置,放在更合适的地方
- 优化脚本执行器,现在它支持同时启动脚本并 await 到脚本执行完成了(之前只能 await complete 信号)
- 优化窗口布局,主窗口打开新窗口时,会根据主窗口位置决定新窗口位置策略
- 优化充当 terminal 教程的文档
- 布局调整后,演出脚本里对逻辑图位置的描述不再符合实际情况。调整为位置无关的描述
- 优化修复阶段演出脚本的节奏,并增加额外的提示
- 爆改轻聊的消息机制,使其和论坛帖子一样使用半动态时间
- 优化第二章里介绍玄云观任务系统的描述
- 优化 demo 结束窗口的创建流程,数据流向更明朗,且去除了冗余逻辑
- 将非必须由 确认通知 触发的后效合并到触发通知的操作触发的后效里
- 优化信息段解锁 popup 样式
- 优化序章的工作手册,现在它更符合世界观了,也有助于玩家顺便理解界面
- 将界面上方的游戏进行天数改成了当前游戏时间
修复:
- 修复 manualfix 在输出通用提示后立刻返回,而不是等待脚本完成执行的 bug
- 修复 ctrl+c 可以停止程序但是重新执行 manualfix 仍显示“修复脚本正在运行”的 bug
- 修复“当玩家选中 xterm 里一段文字,然后点击右键复制,再在原地点击右键粘贴,随后按下任何按键尝试继续输入,都会导致 xterm 被大范围选中”的 bug(再次)
- 修复玩家强退游戏的话第一章第二个任务可能无法被正常解锁的 bug
- 修复第一章结束时没有等玩家确认,界面就被自动重载的 bug
- 修正第二章的一些文案错漏
- 修复在重启游戏后已解锁的 App 未出现的 bug
- 修复调试面板的剧本事件显示异常 bug
- 修复具有多个 action 的事件没有被正常触发 action 的 bug
删除:
- 设置界面不该保存更新日志和愿望单 URL
主要进展内容/本期关键判断点
我做出了哪些「如果错了也要付代价」的判断?
那个导致 xterm 被大范围选中的 bug
上一期的修复只能说临时 patch,但我怎么想怎么膈应,于是本期就花了半天认真研究了这个 bug 进行了根因的修复……或者说相对根因的修复。
问题路径推断是这样的:
- 在 xterm 中选中文本
- xterm 内部处理了选中区域
- 点击鼠标右键复制文本到剪贴板
- 向 xterm 索取选中区域的文本,写入剪贴板
- 使用
xterm.select(0,0,0,0)清除选区
- 再次点击鼠标右键将文本粘贴到 input
- 将剪贴板内容插入 input
- 对 input 进行任意会导致字符变更的编辑
- 这个会触发 input 的更新,主要是对齐 input
- input 更新时会自动发送
\u001b[?25l给 xterm 要求隐藏默认光标- 可能是 xterm 内部有什么逻辑导致其和
xterm.select(0,0,0,0)共同作用,导致了当前视野内的内容被全选
- 可能是 xterm 内部有什么逻辑导致其和
- input 更新时会自动发送
- 这个会触发 input 的更新,主要是对齐 input
我真的不是很想动 xterm 这破玩意,所以我干脆把xterm.select(0,0,0,0) 的调用去掉了,而问题也确实得到了解决。
我觉得自动清除选区也确实算不得是个多有用的特性,所以这样应该就好。
第一章第二个任务未被正常解锁的 bug
这个 bug 是个很有代表性的 bug。
在设计之初,任务系统的工作循环是:
- 启动程序
- 检查尚未发放的任务里,依赖任务全部完成的进行发放
- 玩家将对应数据段拖入提交槽
- 当所有关键提交槽都被满足时,任务标记完成
- 信息收集类数据段通过点击文段/网页文本中对应的关键词获取
- 编程类数据段通过提交代码文件到代码验证器验证通过后获取
- 回到步骤 2
这个乍看没啥问题(或者说至少当时是个游戏开发菜鸡的我没看出来),但是模拟下玩家操作就会发现它存在很多问题:
- 启动游戏
- 发现多了一个编程任务
- 写程序
- 将程序拖到代码验证器
- 将信息段拖到任务提交槽
- 立刻收到下一个任务
其中最大的问题是以下两个:
- 步骤 4 和步骤 5 实际上是机械式重复劳动;
- 步骤 6 会让玩家没有缓冲事件,让玩家感觉自己在被任务追着跑。
所以我将任务系统改成了如下形式:
| 修改前核心循环 | 修改后核心循环 |
|---|---|
| 检查尚未发放的任务里,依赖任务全部完成的进行发放 | 检查尚未发放的任务里,依赖任务全部完成的进行发放 |
| 玩家将对应数据段拖入提交槽 | 玩家将对应数据段拖入提交槽 |
| 当所有关键提交槽都被满足时,任务标记完成 | 当所有关键提交槽都被满足时,任务标记完成 |
| 信息收集类数据段通过点击文段/网页文本中对应的关键词获取 | 信息收集类数据段通过点击文段/网页文本中对应的关键词获取 |
| 编程类数据段通过提交代码文件到代码验证器验证通过后获取 | 编程类代码文件本身就被视为数据段,拖入提交槽自动进行验证 |
| 检查尚未发放的任务里,依赖任务全部完成的进行发放 | 玩家手动点击右上角的“下一循环”(相当于“下一天”)按钮,进入下一循环后发放新的任务 |
两个核心问题都得到了解决:
| 问题 | 解法 |
|---|---|
| 步骤 4 和步骤 5 实际上是机械式重复劳动 | 让编程代码文件本身就是数据段,优化掉了显式的代码验证器 |
| 步骤 6 会让玩家没有缓冲事件,让玩家感觉自己在被任务追着跑 | 玩家点击“下一循环”的动作会让玩家有“我准备好了下一个任务”的心理预期,进而有更强的掌控感 |
当时我十分想当然地认为这不会有什么冲突,毕竟流程本身没啥变化。
但我忽略了一个限制:第一章是在分秒必争地修复系统,不能一天修一个。
而我之前的任务解锁顺序是模块 2 的修复依赖模块 1 的修复,模块 3 又依赖模块 2。但当玩家不能点击“下一循环”的时候,这就导致模块 1 修复后模块 2 的修复任务始终不能解锁。
马后炮地说,我本该在做这种修改时仔细思考下“什么情况下不能下一循环”,但当时确实没往那地方想。
要预防这个问题,我想我应该需要在变更分类时多问一句:
- 这个变更是否引入了新的隐性前提条件?
- 如果是,这个前提在当前所有游戏状态(阶段、模式、解锁状态)下是否都能被满足?
- 修改"任务发放时机" → 前提:玩家能主动触发发放时机 → 反问:有没有哪个阶段里玩家不能或不该主动触发? → 第一章恢复模式
对于这个场景,也可以更收窄一步:凡是引入"玩家必须主动执行某个操作才能推进"的新机制,都需要检查是否存在任何阶段让这个操作不可用或叙事上不合理。
游戏时间的幺蛾子
背景:
- 玩家在游戏里可以使用聊天软件和 npc 交互
- 玩家可以在游戏里决定什么时候使用“下一循环”来推进时间触发新时间
当前机制大致如下:
- 玩家通过完成任务、进入新的 stage 等方式触发 ChapterEventConfig 里的对应 trigger
- trigger 触发 send_chat_message_action 让 simple_chat 即时出现新消息
问题: 假设玩家把同一循环分两真实天玩,前一真实天 20 点完成了循环内的任务 A 触发了对话 1,后一真实天 18 点完成了循环内的任务 B 触发了对话 2,那么玩家就会看到自己先收到的消息 1 的时间晚于后收到的消息 2 的时间。
解决方案: 消息分为即时消息和下一循环的消息:
- 即时出现在 simple_chat
- 类似 laurence042 的“从历史记录恢复的消息”,出现时使用消息里写的时间
- 和其他 ProxyOS 交互得到的消息,取决于游戏时间(具体计算见下文)
- 下一循环出现在 simple_chat
- 其他人类(或者说表现为人类的)npc 的消息,会在玩家选择下一循环后才会收到,使用玩家上一次“下一行循环”时的游戏时间和消息里写的时间加权计算(具体计算见下文)
游戏时间: 设定上,如果没有外部干扰,玩家在“结束本循环”后会在下一个循环的 4:05 被唤醒。随后每一次“休眠维护”都会导致时间往后推进,推进的时间与现实经过时间成正比,每一现实天都会导致推进时间+1 小时,当游戏时间到达 20:05 后,“休眠维护”不再导致时间推进。
加权计算:
(消息里写的时间-4:05)/(上个循环时长-4:05)=(显示的时间-玩家上个循环结束时的游戏时间)/(上个循环时长-玩家上个循环结束时的游戏时间)
如果消息时间早于 4:05,那么作为当前循环的时间显示。
通过这种方式,可以有效避免聊天软件里的时间出戏。
同时我也吸取之前任务没正常解锁的教训了,仔细寻思了下,意识到帖子系统也有类似问题。不过帖子相对而言没那么容易出戏,所以我只是给 manifest 加了个总是本循环 4:00 的更新时间字段。这样就能解释为啥玩家上一个循环玩到 20:00 了,还没看到下一个循环中会出现的时间戳在 18:00 的帖子。
将非必须由确认通知触发的后效合并到触发通知的操作触发的后效里
以测试视角重新审视游戏后,我意识到自己不该搞 stage_change -> notification -> notification_confirmed -> add_chat_message 这种链路,而是应该让 stage_change 直接触发 add_chat_message。
因为如果依赖 notification_confirmed 的后效,那么就会出现如下问题场景:
- 玩家进行一次性操作 A,这个操作的副作用包含触发存档
- 触发通知
- 因为各种原因,玩家强制终止了游戏
- 玩家再次打开游戏,无法再次进行操作 A,通知也无法打开,通知的后效无法触发,游戏进度卡死
因此更合适的方案是把通知的后效合并到操作 A 的后效里。
但有些地方确实需要确认通知后才能触发后效,比如序章里“系统自删除通知”就必须等玩家看完了之后才能进入第一章。这种情况下要避免漏触发、重复触发,就得费点心思了。
我的方案是添加 StageEnteredTrigger,它会在游戏启动时触发,然后根据 required_completed_task_id 和 blacklist_task_id 的设置决定是执行对应行动还是静默。
如果
required_completed_task_id配置的任务尚未完成,那么StageEnteredTrigger对应的行动不会执行。目前主要用于序章里“玩家确认通知后进入第一章”的异常退出自愈。- 正常逻辑:进入序章 -> 任务没完成,静默 -> 任务完成 -> 触发通知 -> 确认通知 -> 进入第一章
- 异常逻辑(强退):进入序章 -> 任务没完成,触发通知 -> 确认通知 -> 进入第一章
如果
blacklist_task_id配置的任务已经完成,那么StageEnteredTrigger对应的行动不会执行。目前主要用于第二章的应用/聊天记录解锁。- 正常逻辑:进入第二章 -> 任务没完成,触发通知、应用解锁、聊天记录解锁 -> 确认通知 -> 完成任务
- 异常逻辑(强退):进入第二章 -> 任务没完成,触发通知、应用解锁、聊天记录解锁 -> 确认通知 -> 完成任务
- 重进逻辑(已完成):进入第二章 -> 任务已完成,静默
瓶颈与问题清单
哪些问题还没解,但也许我已经知道“它们不是什么”?
本期主要瓶颈在于第一章的历史遗留 Bug 修复(特别是 Terminal 交互和任务状态机异常)占用了大量预期用于打磨 Demo 的时间。虽然核心逻辑已理顺,但 Demo 的整体完整度仍需打磨。
下期计划
人总得有点梦想对吧……
我有梦想一周了,下期说啥也得搞定了
- 一个打磨完的 demo
试玩版
预计第一个可玩版本将在第二章的主线内容完成后推出