页面能打开,不代表规则正确:单 HTML 游戏的纯规则核心与不变量测试

发布时间:2026/9/9 14:58:53

页面能打开,不代表规则正确:单 HTML 游戏的纯规则核心与不变量测试 我以前给浏览器小游戏做回归时最容易得到一种虚假的安全感页面能打开点击开始后没有报错Canvas 不是空白手机宽度没有横向滚动条。这些都应该检查但它们只能证明界面大致活着。它们回答不了另外三个问题编程解谜发布的 12 个关卡是否真的都存在一条符合预算的解证件审查显示允许入境时违规原因是否一定为空生态模拟连续推进数百年后是否会出现负种群、NaN或超过上限的环境值这三个问题无法靠截图回答也不适合全部交给端到端点击脚本穷举。我最后采用的做法很朴素把规则核心从 DOM 事件里拆出来让界面和测试调用同一套函数。项目仍然是零依赖、单 HTML 文件。没有为了测试把它改成框架工程也没有增加后端变化只发生在代码边界。本次验证范围这次重构对应三个不同类型的规则系统规则类型核心问题验证样本核心入口编程解谜关卡是否可解指令预算是否真实12 个参考程序simulate(levelIndex, program)证件审查裁定与违规原因是否一致6 日 30 宗档案evaluateCase(day, data)生态模拟长期状态是否始终处于合法边界6 个生态区 × 45 年advanceYear(input, scenario, weatherIndex)专项测试还会实际操作三个页面并检查三星、跨设备档案、Canvas 像素和 390×844 触屏布局。随后执行全仓审计共覆盖 100 个游戏在桌面和手机上的 200 个页面组合。结果为加载失败 0、JavaScript 错误 0、控制台错误 0、横向溢出 0、viewport 缺失 0。这里需要区分两层证据模型测试证明特定规则约束成立浏览器审计证明这些规则接入真实页面后没有破坏运行和布局。两者不能互相替代。先划清四层边界图 1场景数据进入纯规则函数得到结构化结果再交给 DOM、Canvas 和存档层三个专项检查都发生在规则核心而不是复制一份测试专用逻辑。原型阶段的代码通常长这样button.onclick () { // 读取页面字段 // 修改全局变量 // 计算结果 // 更新 DOM // 顺便保存 localStorage };它不是写错了而是把五种职责塞进了同一个入口。当内容只有一关、一个旅客或一个年度时这种写法很快。内容量增加以后问题开始出现测试必须点击按钮才能触发规则想验证一个边界要先把页面操作到指定状态结果文本可能和真实布尔裁定来自两套if随机输入让失败难以复现存档导入后很难单独确认数据清洗是否正确。重构后的边界分成四层场景数据关卡、日规、气候序列和目标纯规则函数只接收输入返回新状态或裁定结果结构化结果状态、原因、趋势和星级所需指标外层适配DOM、Canvas、音效、键盘、触屏和localStorage。测试不再模拟另一套规则而是直接调用第二层。三种规则三种验证方式图 2三个页面分别处理指令执行、证件裁定和长期模拟截图证明它们真实运行规则正确性则由页面内同源核心和专项测试共同给出。这三类玩法不能只写一个通用的测试成功断言。它们需要不同的可验证对象。1. 可执行关卡夹具参考解必须走同一解释器编程解谜最危险的数据问题不是页面崩溃而是关卡看起来完整却没有合法解。我给每个关卡保留一条参考程序{ name: 最终上传, map: [ ############, #..S~.O####, ############, ####O..D.cG#, ############ ], budget: 8, par: 11, solution: F3JFF33F }参考程序不是显示给玩家的答案而是关卡数据的可执行夹具。页面启动时校验器会检查地图每一行宽度一致起点和终点各有一个传送环成对出现参考程序没有超过源码预算程序通过真实解释器执行后收齐芯片并到达终点实际执行步数没有超过关卡目标。核心模拟函数与玩家点击运行时使用的是同一个function simulate(levelIndex, program) { const expanded expandProgram( Array.isArray(program) ? program : [...program] ); const runtime createRuntime(levelIndex); if (!expanded.ok) { return { ...runtime, reason: expanded.reason, expanded: [] }; } for (const token of expanded.commands) { executeCommand(runtime, token); if (runtime.stopped || runtime.won) break; } return { ...runtime, expanded: expanded.commands }; }这次校验确实抓到过问题部分参考解把×2/×3理解成重复整个分组而解释器实现的是重复上一条原子指令。页面可以正常打开地图也能画出来但 10 个参考解与真实语义不一致。如果没有让参考解通过同一解释器这类问题很容易被当成以后手测关卡再说。2. 原因型规则函数结果和解释必须同源证件审查的旧结构会先随机制造旅客再在多个位置判断姓名、国籍、有效期和许可章。这种写法容易出现一种尴尬状态界面说拒绝日志却给出无拒绝理由。新的规则函数不只返回布尔值而是先收集原因function evaluateCase(day, data) { const rule DAY_RULES[day]; const reasons []; if (data.expiry rule.expiry) { reasons.push(护照有效期早于 ${rule.expiry}); } if (rule.banned data.nation rule.banned) { reasons.push(${rule.banned}公民暂停入境); } if (rule.permit (!data.hasPermit || data.permitName ! data.name)) { reasons.push(data.hasPermit ? 通行证姓名与护照不符 : 缺少临时通行证); } return { allowed: reasons.length 0, reasons }; }这样有两个直接收益。第一allowed不再和解释分开维护。只要原因数组非空就必须拒绝。第二UI、日志和测试都能复用reasons。玩家收到的是具体规则反馈测试也能检查item.allowed (item.reasons.length 0)六日 30 宗档案使用确定性索引生成。同一个day slot重复构造得到的对象必须完全一致。这里的确定性不是取消变化而是让变化来自可追踪的规则和数据而不是无法复现的Math.random()。3. 纯状态转移长期模拟先守住不变量生态模拟的问题不是某一年算对就结束了。一次很小的比例错误可能在第 20 年、第 40 年才变成负种群或NaN。如果每次推进都依赖 DOM 和动画长时间压力测试会很慢也很难定位。年度演替因此被抽成纯状态转移function advanceYear(input, scenario, weatherIndex) { const weather WEATHER[weatherIndex]; const next { ...input, pop: [...input.pop], projects: { ...input.projects }, extinctions: [...input.extinctions] }; // 根据营养层、环境和气候计算 next.pop next.water Math.round(clamp(next.water, 10, 100)); next.soil Math.round(clamp(next.soil, 10, 100)); next.habitat Math.round(clamp(next.habitat, 10, 100)); next.year input.year 1; return next; }函数不读取按钮、不绘制 Canvas也不写存档。测试可以从六个生态区的初始状态出发各自连续推进 45 年共检查 270 个模拟年for (let year 0; year 45; year) { sample advanceYear( sample, scenario, scenario.weather[year % scenario.weather.length] ); assert(sample.pop.every((value) value 0 value 999)); assert([sample.water, sample.soil, sample.habitat] .every((value) value 0 value 100)); }这不是为了证明数值平衡完美而是先守住最基本的不变量所有数值有限种群不为负环境指标不越界输入状态不会被意外原地污染同一气候序列能够重复运行。浏览器测试仍然需要把规则函数拆出来不代表可以删除 Playwright。纯函数不知道下面这些事情玩家按键是否真的进入程序队列裁定按钮是否调用了正确的旅客数据十五次推进一年是否更新了图表与结算档案码是否能经过页面入口导出、清洗和导入Canvas 是否绘制了非空且有颜色差异的像素390×844 手机视口是否横向溢出。因此专项脚本保留真实浏览器层node promo-video/scripts/check-rule-driven-games.mjs图 3专项测试验证 12 个参考程序、30 宗确定性档案和 270 个模拟年全仓审计覆盖桌面与手机共 200 个页面组合。本轮浏览器错误、加载失败和横向溢出均为 0。专项测试实际执行了这些动作在编程页面点击四次前进运行后确认首关获得三星按规则完成第一日五宗裁定确认 5/5、三星和下一日解锁建成滴灌网络连续点击推进 15 年确认四项目标、零灭绝和三星对BYTE2、CHECK2、ECO2三种档案码执行往返在桌面和触屏上下文中收集浏览器错误并检查布局。然后再运行全仓审计node promo-video/scripts/audit-games.mjs模型测试负责规则是否满足约束浏览器测试负责真实页面是否正确接入。这比让端到端脚本承担所有组合更快也更容易在失败时定位到具体层。这套方法没有证明什么自动化结果必须和它真正覆盖的范围一致。参考程序不等于关卡质量12 个参考程序全部可解只能证明至少存在一条合法路径。它不能证明关卡只有一条解难度曲线一定合理玩家能自然理解新指令最优解没有更短。要回答这些问题需要搜索算法、游玩数据或人工测试而不是把可解写成好玩。270 个模拟年不等于生态模型真实不变量测试能发现负数、越界和非数值但不能证明生态参数符合现实世界。这里的模型服务于策略游戏反馈不是科学预测工具。文章中的 270 年是自动化执行规模不是实验或现实数据。确定性档案不等于覆盖所有证件组合30 宗档案覆盖当前六日规则但姓名、国籍、目的和多重违规仍然存在更多排列。后续如果规则继续增加更适合引入属性测试或组合生成而不是无限手写固定案例。无浏览器错误不等于所有设备兼容本轮真实检查的是 Playwright 驱动的桌面 1440×900 与移动端 390×844 上下文。它不能替代 Safari、低性能设备、辅助技术和真实触摸延迟测试。什么时候值得这样拆不是每个 200 行小游戏都需要完整测试架构。当项目出现下面任意两项时我会优先拆规则核心相同规则被 UI、日志、结算和存档重复使用内容数据开始批量增加存在随机或长时间演化失败后很难判断是界面错误还是规则错误需要跨设备档案或版本迁移每次改数值都必须手工重玩十几分钟。最小可行改法也不复杂让规则函数接收明确输入返回结构化结果不直接修改 DOM给场景数据保存可执行样例或预期原因为长期状态写出不会被破坏的不变量最后用少量真实浏览器流程验证接线。结语单 HTML 不等于只能写一次性脚本。即使所有 CSS、界面和 JavaScript 都在一个文件里仍然可以划分清楚的模型边界数据描述问题纯函数执行规则结构化结果解释发生了什么页面只负责交互和呈现。这样做最直接的价值不是测试数量更多而是规则开始能够独立回答这关为什么可解这个人为什么被拒绝这个状态为什么仍然合法相关游戏源码、专项脚本和全仓审计都在开源仓库https://github.com/wangzifan396-wzf/mini-browser-games当页面能打开时测试才刚刚开始。真正需要被验证的是按钮背后那套会持续产生结果的规则。
延伸阅读

更多相关文章

2026/9/8 7:24:29

SAGE框架:子目标条件化动作生成在强化学习规划中的应用

在强化学习和机器人控制领域,如何让智能体在复杂环境中高效规划并执行动作一直是个核心挑战。传统的规划方法往往面临计算复杂度高或难以处理高维状态空间的困境。近期提出的 SAGE(Subgoal-Conditioned Action Generation)框架,通…

2026/9/6 1:23:01

Linear Loops自动化工作流:提升团队开发效率的完整指南

在项目迭代和团队协作中,重复性的任务流转、状态同步和跨工具数据搬运往往消耗大量开发时间。Linear 最新推出的 Loops 功能,正是瞄准了这一痛点,旨在通过自动化工作流简化循环工程操作。本文将完整解析 Loops 的核心概念、适用场景&#xff…

2026/9/8 13:51:29

60%的知识库文档从未被检索过——你在用20%的文档回答100%的问题

核心观点:知识库不是“越多越好”。我去跑了一个查询,盯了半天——六成的文档,过去30天一次都没被搜过。 我去做了件大多数人不会做的事:给知识库里每一条文档,查一下它过去30天被检索过多少次。 1200条FAQ。我按检索…

2026/9/10 3:46:21

openARTmini多任务协同:数字识别与智能寻迹融合实践

简介:本资源是面向电子设计竞赛(电赛)参赛者与嵌入式AI初学者的OpenARTmini数字识别与寻迹融合实战方案,聚焦视觉感知与自主导航协同实现,解决小车类智能体在复杂光照、多角度条件下的高精度数字识别(98.7%…

2026/9/10 3:46:21

CANN/ge LLM-DataDist C++专题

专题 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的友好…

2026/9/10 3:41:20

macOS开源效率工具盘点:截图录屏取色OCR全能菜单栏应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码