下载 App
07 · 我做了很久的游戏,才想起来测"能不能输"
《临界》开发者日志 · 第七篇 前六篇讲我怎么把东西做出来。这篇讲我怎么发现自己做的不是游戏。


【一个我从来没测过的问题】
我在这款游戏上花了很久:地形渲染、SOC 内核、幂律验证、性能优化、音频、关卡、引导。
每一轮我都做"改完必查语法、必看画面"。我以为我很严谨。
直到某一轮,我盯着 ROADMAP 的验收标准发呆——上面写着"核心玩法完整可玩"。
"可玩"是什么意思?
我意识到自己从来没测过一件最基本的事:
如果玩家什么都不做,会发生什么?


【于是写了个测试:什么都不做】
PASSIVE L1 初雪缓坡 day=6/6 status=playing vMax=0 nat=0 PASSIVE L2 菱形山脊 day=8/8 status=playing vMax=0 nat=0 PASSIVE L3 峡谷雪道 day=9/9 status=playing vMax=0 nat=0 PASSIVE L4 孤岛雪峰 day=10/10 status=playing vMax=0 nat=0 PASSIVE L5 临界 day=12/12 status=playing vMax=0 nat=0
整局不引爆、不操作——五个关卡全部通关。村庄积雪恒为 0,自然雪崩 0 次。
我盯着这个结果看了很久。
我做的不是一款"很难的游戏",而是一款"没有失败可能的游戏"。


【根因:村庄根本没在积雪】
-- 旧实现:村庄积雪只在【雪崩结束时】同步 for idx, c in pairs(self.villageCells) do c.snow = self.world.snow[idx] end
问题在于:村庄格上其实一直在下雪(每天的 Load() 给每个格加雪,包括村庄格)。
但这段代码只在雪崩结束时去读它。而被动玩法几乎不产生雪崩——所以村庄永远显示 0 雪。
物理上村子在被埋,游戏只是没去读。
修复:把同步提取成 Game:SyncVillage(),在 NextDay() 里每天也调用一次。
修复后:被动玩法的村庄积雪从 0 涨到了 13~17。威胁是真的了。


【但我随即发现第二个问题】
修好可败性之后,我又测了一件事:
主动玩(频繁引爆)和被动玩(什么都不做),结果有区别吗?
L5 被动=17.7 主动=17.2
几乎没有区别。
而这个游戏的整个设计承诺是:
你的工作是在雪崩发生之前,主动引发小雪崩释放压力,避免一次失控的大雪崩埋掉村子。
如果主动和被动结果一样,这个承诺就是假的。


【我一层层往下挖】
第一层:村庄积雪用 c.snow = world.snow[idx] —— 小雪崩和大大雪崩对村子的作用完全相同(都是把该格雪深写成同一个值)。
→ 改成独立累积器:VillageDeposit() 只累加正增量。
第二层:自然崩塌的沉积量取决于它是否恰好流经村庄格——那是随机的。乘以 3 倍也只是放大一个随机的小数。
→ 改成与雪崩规模成正比:失控释放 = 整条雪道冲向村子。
第三层:玩家的引爆也在往村里堆雪,24 次引爆的累积超过了自然雪崩。
→ 加 villageReach 到达阈值:规模不够的雪崩在坡上就耗散掉了,根本到不了坡脚的村子。
这一层终于让设计成立了:频繁释放 = 雪崩都很小 = 村子安全;攒着不炸 = 一次失控大雪崩 = 埋村。


【然后我撞到了墙】
我做了最后的验证——测量危险度:
DANGER L4 被动 max=0.45 | 主动 max=0.45 DANGER L5 被动 max=0.45 | 主动 max=0.45
完全相同。
我想了很久才明白为什么:
Detonate 只是把雪重新分配——雪崩守恒,总雪量不变。
而 Load 每天给每个格加 0.10~0.14 的雪(约 922~1290 单位/天)。
唯一的雪槽是边界耗散,而驱动量远大于它。
于是系统永远自动稳定在自然崩塌的阈值上。玩家引爆多少次,都改变不了这件事。
玩家在"降低危险度"这件事上,注定是旁观者。
我试了三种修复(边界额外损耗 edgeLoss、降低清雪、提高到达阈值)——危险度始终不变。


【我决定停下来】
我盯着 ROADMAP 上"投稿倒计时"发呆。
要真正实现这个设计,需要重新设计驱动/耗散的关系——例如把 Load 降到与玩家水槽同量级,或者让"受控爆破"成为唯一有效的雪槽。
那是需要单独一轮设计的工作,不该在投稿前动。
于是我把 villageLimit 标定到「被动实测最大值 × 1.2」——保证正常玩法能赢、有张力、疏忽会输。
然后我把整个失败过程写进了 ROADMAP,包括我试过什么、为什么没用。


【顺带发现的第三个问题】
我在整理投稿材料时,随手看了一眼 LEVEL_INDEX:
local LEVEL_INDEX = 5 -- 硬编码 = 最难的「临界」
两个后果:
- 评委的第一局就是最高难度——12 天、最高降雪、最窄村庄上限
- 我做的另外 4 个关卡,在正常游玩中根本不可达
我做了 5 个关卡,却只交付了 1 个。
修复:从第 1 关开始,赢了进下一关,输了重试本关。


【三个问题,同一个根源】
回头看,这三个问题有一个共同点:
・不可能输 —— 这一关好不好玩 —— 玩家的操作有没有后果 ・主动无收益 —— 数值调得对不对 —— 设计的承诺成不成立 ・4 关不可达 —— 关卡做得好不好 —— 玩家怎么进入其他关
我一直在验证"我改动的东西",而不是验证"产品整体"。
每一轮我都做了语法检查和截图——但那只是确认"我的改动没坏",不是确认"游戏是游戏"。


【我学到的事】
【1. 测"什么都不做"应该是最早的测试】
它只花了十分钟,却暴露了三个月的盲区。如果我在第一周就测它,后面所有的工作都会更有的放矢。
【2. "改完必看画面"不够,要"改完必测承诺"】
看画面能发现渲染错误,但发现不了设计失效。设计失效要用对照实验才发现——被动 vs 主动、A 组 vs B 组。
【3. 承认做不到,比假装做到了好】
这一轮我的产出里,有一半是"我试了、没用、原因如下"。
我本可以在日志里只写"加了到达阈值,设计成立了"——那部分是真的。但危险度不变这件事也是真的,而且更重要。
如果我不写,下一个接手的人会以为这个设计已经完成了。
【4. 在正确的时间停下来】
我在这三个问题上投入了很多轮。但在投稿截止前,有些问题应该被记录,而不是被解决。
判断"什么时候该继续挖、什么时候该收手"——这可能是这三个月里最难的一课。




【后记(2026-10-06 夜补)】
这篇里那句「保证正常玩法能赢、有张力、疏忽会输」—— 后半句被实测推翻了。
投稿前做四关卡回归时发现:第 4 关「孤岛雪峰」用四种不同打法各跑一遍,四次全部被埋, 导致第 5 关与无尽雪季完全不可达 —— 也就是说那个「标定」在第 4 关根本没成立。 (完整经过写在 docs/devlog/14.md。)
修完之后再量一遍:四种打法都能通关;第 5 关最紧的一次村子到 5 / 7(71%)。 所以「有张力」成立,「疏忽会输」在战役里目前不成立 —— 失败体验实际上跑到无尽雪季去了。
不改正文,是因为这篇记录的是当时的判断和做法;但那句承诺,现在必须在这里标出来。
【后记之二(2026-10-08 补):正文里「危险度始终不变」这句也被推翻了】
正文里有两次写到「危险度始终不变」(「我试了三种修复……危险度始终不变」、「危险度不变这件事也是真的」)。 那是当时的观察,但结论不成立。
2026-10-08 跑了项目自己的实验脚本 tests/experiment_village.lua。第 5 关,同一关、同一天数:
・被动(不炸) —— 0.343 —— 26158 —— 109.3 / 7.4 ・主动(每天炸) —— 0.038 —— 20571 —— 5.6 / 7.4
同一关、同一天数,炸与不炸的危险度相差约 9 倍。
所以正确的说法是:危险度不是「不变」,而是「它是打法的函数」 —— 当时之所以看着不变,是因为我一直在用同一种打法观察它,没有把打法当作变量。
(正文不改,理由与上一条后记相同:这篇记录的是当时的判断。但这句结论必须在这里标出来。)
【后记之三(2026-10-08 深夜 补):「疏忽会输」也已经在战役里成立了】
第一篇后记里写「『疏忽会输』在战役里目前不成立 —— 失败体验实际上跑到无尽雪季去了」——这句现在已经过期。
2026-10-07 的阀门修复之后,同一套 90 局实验(五关 × 六种子 × 三种打法)测得:
- 有节制(约每两天炸一次):29/30
- 拼命炸(能炸就炸):28/30
- 完全被动(一次都不炸):12/30 —— 而且只看第 3~5 关,是 0/18
也就是说:不炸的话,从第 3 关起会被一次自然大雪崩直接推平村子。 「疏忽会输」在战役里成立了,不再只靠无尽雪季兜底。
(正文与前面两条后记都不改,保留当时的判断;这条只把现在的实测结果标出来。)
【后记之四(2026-10-10 补):上面那三个数也已经过期】
同一段落里的 29/30 · 28/30 · 12/30 是 2026-10-07 阀门修复之后的测量 ✓ —— 当时为真 ✓。 2026-10-10 调了第 1、2 关的承载阈值(4 → 3.5)之后重测:28/30 · 25/30 · 被动 0/30 ✓
⚠ 同日再更正一次:那个「被动 0/30」是该实验 6 个种子下的结果 ✗ —— 换 30 个种子(1..30)复测是 4/150 = 3%:第 1、2 关各赢 2 局,第 3~5 关全败 ✓。 所以准确的说法是「一次都不炸基本赢不了」✓,而不是「一定输」✗。
⇒ 这一段若被当作当前值引用会误导 ✗;引用请用 docs/measurements.md §1 ✓。 (这篇本来就是「事后翻自己几小时前的结论」,多一条同类后记不算跑题 ✓。)
《临界》· 2026 TapTap 聚光灯游戏创作挑战*



