没有连击条,没有登录奖励,没有体力倒计时。一款叫NICHLYST的游戏,把注意力当成资源来配给——就像它故事里那座避难所配给燃料和食物一样,严格、有限,每一笔消耗都记在游戏状态真实的资源账本上。
这是一款反多巴胺的档案生存模拟游戏,为RevenueCat的Shipaton 2026而做。故事发生在虚构城市Myrne的地下档案室,那是一座虚构的、被围困的东欧国家的城市。四十天,一名档案管理员,来访者可能说真话也可能不说,33种结局,以及一个会随着故事崩塌而物理性腐烂的界面。
警报声就是节奏
开发它的物理环境本身就是故事的一部分。2026年9月,战时乌克兰,一台老旧的AMD A4处理器,8 GB内存。在那座城市里,民防警报大多数日子都会响,但你不是每次都往避难所跑——否则你就得住在那儿了。
警报声成了工作的节奏。它哀鸣时,工作继续,除非打击近到值得在意。当它变成又一种环境音,你继续干活,只希望一块流弹碎片别从窗户飞进来。打击猛烈时,你就在走廊里等最糟的那阵过去。
这意味着任何东西都不能依赖长时间运行的工具链、模拟器,或者第一次渲染前就吃掉4 GB的热重载服务器。Unity、React Native、Flutter,全部出局——每一个都是多GB的运行时,向一台负担不起这个问题的机器提问。
答案是做减法:整个引擎是原生JavaScript(ES6+)、CSS3自定义属性和HTML5,用Apache Capacitor封装成安卓应用。没有值得一提的构建步骤,没有虚拟DOM去协调游戏循环。
叙事负载不是一个大文件
完整的叙事负载并非单个巨型文件。清单驱动的加载器在启动时并行拉取meta、codex、endings,以及八个模块化的日块,放在data/days/目录下:prologue、act_1到act_4、postscript、hidden_postscript、after_end。每个都是小JSON文件,启动时一次性并行加载并常驻内存,另有一个打包好的game_data.json作为模块化失效时的兜底。
浏览器引擎就是运行时,而它已经在手机上了。有用性的证明是一件可交付的成品。NICHLYST在Shipaton 2026的Devpost提交,排在2026年9月30日截止日期之前。Google Play强制的14天封闭测试通道正在运行,十三名真人测试者分布在不同时区,从Redmi Note 9 Pro起步的机型上试玩——那时火箭还是一个通知类别,不是隐喻。
截止日期是9月30日,这篇文章9月23日发出,因为HackerNoon的编辑队列要3到5个工作日,而错过时机和错过质量,看起来没有区别。
调色板由Git钩子强制执行
在这里,设计就是工程,而最有争议的产物——一套调色板——由一个提交前的Git钩子强制执行。美学是地下档案室:低饱和的赭石、铁锈、灰色,压在近黑之上。调色板只有五种颜色,别的没有:#1A1C1C、#2D3131、#7D7263、#BCB2A1、#EDE6D8。
墨色(#EDE6D8)压在背景色(#1A1C1C)上,对比度达到13.78:1,远超WCAG AAA标准。调色板以CSS自定义属性(--c-1到--c-5)的形式存在于单一事实来源中,每个界面消费的是令牌,从不用原始十六进制值。
选择按钮保证48px的触摸目标,布局尊重env(safe-area-inset-*)以适配刘海屏手机,所有动画效果在prefers-reduced-motion下退化为无。凌晨两点你会变马虎,不管有没有空袭警报,惯性会让一个跑偏的颜色溜进去——除非有什么东西拦住它。自动化关卡没有惯性。
调色板由scripts/check_colors.sh执行,一个53行的提交前钩子,它比对暂存文件列表,扫描CSS、JS和HTML中的十六进制字面量,逐一对照白名单正则校验。任何跑偏的颜色都会中止提交。正则接受6位和8位(含alpha通道)十六进制,大小写不敏感,别的都不接受。
它会自我约束:二进制文件、字体、音频、www/dist和钩子自身都被排除在外。仓库版本用乌克兰语记录违规,带严重级别标记。承重的部分是失败模式:提交前钩子里的exit 1是硬停。美学成了版本控制的不变量——这道关卡在深夜编辑时抓到过跑偏的十六进制,而那正是人类审查者会放行的时刻。
界面会腐烂,而且是被令牌化的
第二套设计系统是腐烂。界面有8个级别的视觉退化,由currentDecayLevel驱动,随着档案在四十天里崩塌,它从0爬到7:资源条淡向不可见,边框变粗,墨色暗向幽灵色调。
界面是一件叙事工具——玩家看着档案像档案管理员一样腐烂——而腐烂是被令牌化的,不是手工调色的,这正是颜色钩子能管住它的原因。
差点失败的是算术:40天、33种结局、130个布尔标志、6条道德轴——一个状态空间,任何未加防护的变更都是三周后才被发现的承重bug。天真的做法是一个全局可变的window.gameState,每个模块都往里写,会产生两类失败:异步系统交错时的竞态,以及资源在写入中途越界时损坏的存档。突破口在www/js/state_manager.js,一个让非法状态无法被表达的状态机。
热门跟贴