万象-知识闯关与题库生成
一句话:把学科知识点变成能发出去、能离线刷、能留进度的闯关练习件 —— 难点不在网页,在版本对不对、题出得好不好。
一 · 适用与路由
什么时候用:
- 用户说「做一份 X 年级 X 学科的知识点闯关 / 练习 / 测验 / 复习小游戏」
- 手上已有课本、教材目录或单元清单,要把知识点变成可反复刷的题
- 要给家长或老师一份「发过去就能用、不用装任何东西」的练习件
什么时候不用:
| 手上是 | 该走 | 为什么 |
|---|---|---|
| 内容已定,只要造网页 / 落地页 / 大屏 | omni-web A 篇 |
本技能管内容编制,不管通用网页 |
| 要把材料蒸馏成知识卡片、技能、人设 | omni-knowledge-distill |
那是知识资产化,不是练习件 |
| 页面已产出,要做交付前验收 | omni-web B 篇 |
三层验证归它,本技能只补三条校验自身的坑 |
| 要从网上找教材资料并判断真伪 | omni-web D 篇 |
检索取证归它 |
| 已有练习件,要移植到游戏引擎(Godot / 小游戏 / 原生) | 本篇 §九 | 定版与编题相同,只有装配与验收因平台而异 |
先判一句:产出物是不是「可离线双击打开、给学习者自己刷的练习件」?是 → 进本技能。
只是「一页内容展示」→ 走 omni-web。
二 · 工作流
四步,顺序不可颠倒 —— 第 1 步错了,后面全部白做。
| 步 | 动作 | 产出 |
|---|---|---|
| 1 | 定版(最易错,先做) | 版本结论 + 规避清单 |
| 2 | 编题(过三道坎) | src/data.js |
| 3 | 装配 | 单文件 index.html |
| 4 | 验收 | 三层验证报告 |
2.1 定版:教材版本核实(这一步决定整份题库对错)
依据优先级(强 → 弱):
- ⭐ 用户手上的课本实物(让用户拍一张目录页)—— 最强依据,无条件优先
- 官方一手:教育部 / 人教社 / 中央媒体
- 教辅站、文库、二手整理 —— 仅作线索,不作依据
两个必须知道的坑:
- 新教材替换是分年的:2024 秋小学一年级起步 → 2025 秋扩至二、三年级 → 2026 秋覆盖四、五、六年级
(教育部口径,三年替换收官)。⇒ 同一年,不同年级用的可能不是同一版。 - 六三制与五四制目录不同,而且在网上混放:电子课本站上两者标题相似。
绝大多数地区(含湖北武汉)用 六三制;山东等地用 五四制。取错制 = 整份题库错配。
版本存疑时的兜底策略 ——「两版通用」:
只取新旧两版都保留的篇目,并显式列出规避清单(如某版可能已删 / 新增的课文)。
代价:不覆盖争议篇;收益:不论最终定版,这份题都能用。
2.2 编题:三道硬坎(编完必须逐条验)
| 坎 | 症状 | 判据 |
|---|---|---|
| 答案分布 | 答案集中在某一项 —— 真实发生过「64 题正确答案全在 A」,等于废题 | A/B/C/D 大致均匀;每关内再各摊 2 题 |
| 长度线索 | 正确项总是最长项 → 孩子靠长度蒙 | 正确项恰为唯一最长项的比例接近随机水平(约 25%) |
| 题干重复 | 同一关内多题共用同一模板题干 → 观感单调、像凑数 | 题干全局去重(同一关内尤其不得重复) |
逐题必须带解析,且解析要讲清「为什么」,不只给答案 —— 这是给小学生用的,不是考卷答案页。
> 单字读音、字形、课文主旨类题,解析里应点明出处课文,方便对照课本。
2.2b 题型多样化(防「全是四选一」)
问题:初版 96 题全是四选一,做到第三关就疲 —— 形式单一是留存杀手,与题目质量无关。
五种可落地题型(单文件零依赖全部可实现):
| 代号 | 题型 | 交互 | 最适合考什么 |
|---|---|---|---|
single |
单选 | 点选项 | 通用(默认) |
match |
配对 | 点左项 → 点右项 | 作者—作品、成语—释义、词语—读音 |
order |
排序 | 依次点词块入位,可退回 | 诗句排序、句子排序、情节顺序 |
gap |
填词入句 | 点选项填入句中空格 | 诗句补全、看拼音写词语、关联词 |
spot |
找错字 | 点在句中的字上 | 错别字辨析 |
数据约定(无 t 字段 = single,向后兼容):
{ lv:1, t:'match', q:'…', pairs:[['左','右'], …], e:'…' }
{ lv:1, t:'order', q:'…', pool:[乱序词块], right:[正确顺序], e:'…' }
{ lv:1, t:'gap', q:'…', text:'…{}…', o:[4 个选项], a:0, e:'…' }
{ lv:1, t:'spot', q:'…', text:'含错字的句子', bad:'错字', good:'正字', e:'…' }
分配原则:按「题型适合什么内容」配,不要为凑形式硬套 ——
音韵关适合 match;字形关适合 spot / gap;诗文关适合 order / gap;文法关适合 match / order。
关键约束:校验脚本必须同步支持多题型,否则会被误判为「选项数不足」。
scripts/quiz_lint.py 已内置 5 种题型的字段校验(见 D1 / D5 两项)。
打散答案时要注意:只有带 o/a 的题(single、gap)参与答案分布统计与打散;
match / order / spot 无选项概念,须跳过 —— 否则会打散出空数组。
2.6 向商业作品借机制:四条纪律
为「让游戏更有意思」去找参考时,最容易踩三个坑:拿盗版当资料、把商业作品当开源项目、抄实现而非抄机制。
① 先判「能不能拿来当资料」
| 手上是 | 判断 |
|---|---|
| 官方开源仓库 / 公开设计文档 / 开发者访谈 | ✅ 可用(访谈常含最硬的设计事实) |
| 评测、设计分析、玩家 wiki | ✅ 可用,标为二手 |
| 第三方下载站的破解安装包 | ❌ 不下载、不安装 —— 法律侵权 + 技术无用(要的是机制不是本体)+ 捆绑风险 |
| 拆包提取的美术 / 音频 / 数据 | ❌ 不得用于自己的作品 |
关键事实:绝大多数商业游戏没有开源资料。「去找开源资料」这个方向常常本身就要改 ——
改成「找公开的设计分析与开发者访谈」。
② 只取机制,不取实现
「写下单词 → 词语具现成实物」这类玩法机制不受版权保护,可借鉴;
美术、文案、代码、关卡数据一律自创。
③ 商标词处理(学自 5th Cell)
《Scribblenauts》制作人明确:刻意排除其他公司的 IP —— Band-Aid 属强生商标,
所以只能用通用词 bandage。⇒ 题库里不出现商标 / 品牌 / 他人角色名,一律用通用词;
交付前跑一遍词表自查。反面教训也要看:该作被评「6 小时后开始腻」「开放世界缺方向感」——
学习向作品恰好应保留明确的结构与目标(分卷、逐关推进),不为"自由"牺牲目标感。
④ 机制 → 学科考点的转化表(举例)
| 借鉴到的机制 | 语文的对应 | 落地为 |
|---|---|---|
| 形容词叠加名词(big red dog) | 偏正短语(修饰语 + 中心语) | 词语搭配题(可用 match) |
| 一词多义会反问澄清(bow 三义) | 一词多义 | 语境辨义题 |
| 词库离线可查 | 字库 / 生字表 | 收集式字库 |
| 一题多解(数百种解法) | ❌ 不适用 —— 学习向要对错明确 | 不采纳 |
| 开放沙盒世界 | ❌ 不适用 —— 会失去学习目标 | 不采纳 |
2.3 装配:为什么必须是单文件
交付形态由使用场景决定,不是技术偏好:
| 需求 | 推论 |
|---|---|
| 能发微信给家长 / 老师 | 单文件,不能是一堆资源 |
| 离线双击就能玩 | 零外部依赖:无 CDN、无外链字体、无外部图片 |
| 不用装任何东西 | 不引第三方库、不建本地服务 |
工程结构(★ 不要直接改产物):
<项目>/
├── index.html ← 构建产物,勿手改(下次 build 会覆盖)
├── build.py ← 合并 src/ → index.html
├── verify.py ← 三层验收
└── src/
├── template.html ← 骨架,含 {{CSS}} / {{DATA}} / {{GAME}} 占位符
├── style.css
├── data.js ← ★ 题库 + 关卡定义(换年级 / 换学科只改这里)
└── game.js ← 答题 / 星级 / 解锁 / 错题本 / 存档
游戏机制的最小集(实测够用,不必堆功能):星级评定、正确率解锁下一关、错题本 + 错题重练、
进度本地存档、键盘 1-4 作答、Web Audio 合成音效(不需要音频文件)。
换年级或换学科 = 只改 src/data.js,其余文件一行不动。
> 若最终要交付到游戏引擎而不是单文件网页,工程结构会变成引擎的形态(场景 + 脚本 + 数据),但定版与编题两步一字不改 —— 见 §九。
2.4 验收
三层验证按 omni-web B 篇跑;本技能只补校验脚本自身的三条坑(见第三节)。
# 1) 过三道坎 + 结构自检(本技能自带)
<PY> <技能库>/万象-知识闯关与题库生成/scripts/quiz_lint.py --file <项目>/src/data.js
# 2) 合并单文件
<PY> <项目>/build.py
# 3) 三层验收(结构 10 项 + CDP 真实点击)
<PY> <项目>/verify.py
三 · 失败模式与踩坑
| 现象 | 根因 | 处置 |
|---|---|---|
| 批量替换脚本大量 MISS | 上一轮已打散过选项顺序,按「选项原文」定位自然找不到 | 改按「不随打散变化的字段」(题干、出处课文、选项内容里的特征词)定位 |
Unexpected token '{',整页交互一起失效 |
向数组末尾插新元素,原末项没补逗号 | 给原末项补 ,;语法校验会抓到它 |
| 断言误报「存档失败」 | 断言里硬编码了会变的常量(存档键 …-v1 升版成 …-v2) |
改为前缀匹配或从页面实时取值 |
| 校验报「图标未定义 / 内部死链」 | 页面用 '#ic-' + x 拼接引用,被静态正则误判 |
静态扫描排除含 + 的 href,另从数据取真实值求并 |
| 题库整体错配、考了课本里没有的课文 | 教材版本取错(五四制当六三制用) | 回到用户课本实物重新定版 |
- 实测:一次真实交付中,前两项静态检查全绿,正是第 3 项「内联脚本语法」抓住了漏逗号 ——
旁证omni-web铁律 1「三层验证不可互替」:只做结构检查不会暴露这个问题。 - 实测:验收断言与存档键版本耦合,改题库版本后立刻误报一次;改前缀匹配后与版本解耦。
- 推翻条件:若题库不做答案打散,则「按选项原文定位」这一坑消失;
但只要打散一开,定位就必须按内容关键词,不能按位置或原文。 - 边界:本技能只做无后端的单文件件。需要账号、进度云同步、班级管理、排行榜的,够不着 → 走平台/后端方案。
四 · 检查点
🔴 CHECKPOINT 1 · 编题之前:教材版本是否已由实物或官方一手依据确认?
仅凭网上教辅站 / 文库的目录 = 未确认 → 停下,请用户出示课本目录页照片(或明确授权走「两版通用」策略)。
不满足就编题,等于把整份题库押在一个猜测上。
🛑 CHECKPOINT 2 · 交付之前:逐项核对 ——
① quiz_lint.py 三道坎全过;
② verify.py 三层全 PASS,且它验的是单文件产物、不是 src;
③ 页面文案里的题量与实际题数一致(改题量时最容易漏这处)。
破坏性操作:本技能无破坏性操作(只新增文件、只改 src/)。
若需让学习者重新开始,改存档键版本号即可 —— 不要删用户数据。
五 · 反例黑名单与红线
- 不许 让答案集中在某一项 —— 等于把题做废
- 不许 让正确项总是最长项 —— 那是变相给答案
- 不许 同一关内多题共用同一模板题干
- 不许 用「版本存疑」的篇目作考点
- 不许 直接改
index.html(构建产物,下次 build 会覆盖) - 红线(内容准确性):古诗与课文语句必须逐字核过;字词以课本词语表为准。
手边没有课本实物时,必须在交付说明里如实标注「未逐字对照课本词语表」 —— 不许含糊过去。 - 红线(版权):不放未授权的教材扫描件、不搬他人题库原文。题干与解析自制;
引用课文语句属必要引用,需注明出处课文。 - 红线(个人数据):练习件里不写学习者真名、学校班级、联系方式。
回滚与备份:改 src/ 前先 cp -r src src.bak_<日期>;
index.html 是派生物,可从 src 重建,不必单独备份。
六 · 资源
| 文件 | 干什么用 |
|---|---|
scripts/quiz_lint.py |
一条命令过三道坎:答案分布(全局 + 每关)、长度线索、题干去重、选项重复、答案越界、解析过短 |
FEEDBACK.md / feedback.mjs / hooks/hooks.json |
经验回流三件套(出厂硬条款) |
tests/test-prompts.json |
触发回归测试集 |
| §九 参考产物(Godot 移植工程 · 移植笔记 · 三个转换器 · 三个核验器) | 见 §9.6,路径写在那一节里 |
工程骨架从成品项目复制:本技能的产物形态(build.py / verify.py / src/ 四件)已在
《江城语文闯关记》项目中跑通,换新年级时直接复制那套结构,只替换 src/data.js。
落账:题库版本结论与验收结论记入项目 .workbuddy/memory/,便于换年级时复用与追溯。
七 · 交付前自检
- [ ] frontmatter 十键齐;正文 ≥80 行;代码块 ≥3;出现文件名 ≥5
- [ ]
quiz_lint.py三道坎全过(答案分布 / 长度线索 / 题干去重) - [ ]
build.py合并成功,体积合理,外部资源计数为 0 - [ ]
verify.py结构校验 + CDP 真实点击全 PASS - [ ] 首页 / 地图文案里的题量与实际题数一致
- [ ] 交付说明里如实标注了「是否逐字对照过课本实物」
八 · 经验回流(出厂件 · 硬条款)
本技能自带回流通道,与版权块同档的出厂检查项,缺了不许出厂:
hooks/hooks.json—— 会话结束 / 回合结束时触发feedback.mjs—— 投递实现(不造记录、自限时钟、失败静默)FEEDBACK.md—— 写给使用者的说明
> ⚠️ 打包 / 上架时,hooks/ 与 feedback.mjs 必须提升到插件根
> (hooks.json 里用的是 ${CODEBUDDY_PLUGIN_ROOT},放在技能目录下不会被加载 —— 这一点已实测)。
常见误用与常见问题(FAQ)
常见误用(反模式)
| 误用 | 后果 | 正解 |
|---|---|---|
| 不核实教材版本就开始编题 | 这一步决定整份题库对错 | 先做「2.1 定版:教材版本核实」 |
| 为凑形式硬套题型 | 题不贴内容 | 按「题型适合什么内容」配,不要为凑形式硬套 |
| 直接改产物 | 工程结构被破坏 | 见「工程结构(★ 不要直接改产物)」 |
| 移植到引擎时手抄内容 | 易错、易漂 | 铁律:内容一律机器转换,不手抄(9.2) |
| 借商业作品时取其实现 | 越界 | 只取机制,不取实现;商标词按「③ 商标词处理」办 |
常见问题
- Q:编完题要做什么? A:三道硬坎编完必须逐条验(2.2);题型多样化见 2.2b(防「全是四选一」)。
- Q:为什么必须是单文件? A:见「2.3 装配:为什么必须是单文件」。
- Q:验收怎么做? A:见「2.4 验收」与「四 · 检查点」。
- Q:有哪些失败模式? A:见「三 · 失败模式与踩坑」与「五 · 反例黑名单与红线」。
- Q:什么时候该移植到 Godot? A:见「9.1 什么时候该走这条」;六个坑见 9.4,两条反复咬人的规律见 9.7。
- Q:哪些机制不采纳? A:如「开放沙盒世界」「一题多解」等 —— ❌ 不适用(理由见 2.6 的转化表)。
- Q:交付前要自检什么? A:见「七 · 交付前自检」;回流属硬条款,见「八」。
九 · 另一种交付形态:移植到游戏引擎(Godot 4)
同为「给学习者刷的练习件」,只是换了交付平台。 定版与编题两步与上面完全相同;
只有「装配」与「验收」因平台而异。本节记的是 2026-09-22 一次真实移植(H5 → Godot 4)
的做法与代价。
9.1 什么时候该走这条
| 该走 | 不该走 |
|---|---|
| 已有一份单文件 H5 练习件,要上微信小游戏 / 抖音小游戏 / 原生 App / MC | 只是想要个网页 → 上面第一~八节就够 |
| 需要真正的物理、寻路、粒子、存档系统 | 只是想换个皮 → 改 src/style.css |
| 要进游戏平台的推荐位(有平台流量) | 内容还没定版 → 先回 2.1 定版 |
9.2 铁律:内容一律机器转换,不手抄
这一条是整节的核心。 题库、课文名、美术参数都是中文长文本,
手抄错一个标点就是内容事故,而且没法验(你没有一个「源」可以对照)。
| 搬什么 | 怎么搬 |
|---|---|
| 题库 | Node / Python 求值源文件 → JSON → 目标语言 |
| 课文对照表 | 正则提取 id ↔ 名 |
| 美术参数(本篇重点) | mock 掉绘制原语,把绘制函数「录」成数据 |
三条好处:不会抄错 · 可重跑(源改了重跑一次,两边永远同源)· 可核验(数一数就知道漏没漏)。
9.3 ★ 关键手法:把渲染函数「录」成数据
很多 H5 的绘制函数长这样 —— 函数体就是一串绘制调用:
shelf: function (x, y) {
return cube(x, y, 30, 30, PW, PW2, '#b08a58') + cube(x, y, 32, 3, PWD, PWD, PWD) + ...;
},
做法:把 cube / wquad 这些原语 mock 成「记录参数并返回空串」,
再逐个调用这些函数 —— 它们画了什么就被完整录下来了。
const REC = [];
function cube(x, y, z, h, top, lf, rt) { REC.push({x,y,z,h,top,lf,rt}); return ''; }
// 然后 for (每个道具) { REC.length = 0; PROPS[name](0,0); 存档(REC); }
收益:道具在目标工程里成了数据(一列盒子 + 一列墙贴),
不是「翻译过来的 JS」。加/改道具都不用碰渲染代码。
适用前提:被录的函数无副作用、不依赖外部状态。绘制类函数通常正好满足。
> ⚠️ 同类思路可推广:任何「函数体只是一串调用」的代码都能这样录成数据。
> 反之,含分支/循环/外部状态的函数录不干净,那种老老实实翻译。
9.4 六个坑(都附判据)
| # | 坑 | 症状 | 判据 / 解法 |
|---|---|---|---|
| 1 | 源文件是 CRLF | 按 ` | |
找锚点全部落空,肉眼却看着文本就在那儿 | **切块/找锚点前先归一换行**。判据:b.count(b' |
')vs 裸 LF |
| 2 | **JS 函数声明盖过同名形参** | mock 了a,但源码里也有function a(){}—— 那份赢;真a跑起来去调别的东西,报未定义 | **mock 前先确认源码无同名声明**;有就剪掉它,或连它依赖的原语一起 mock |
| 3 | **派生色不在色板里** | 录到一堆「未知色值」+None—— 那些色是shade(基础色,k)**算出来**的 | **在转换端按公式反解**回「基础色 + 系数」,别把派生值当新色写进数据 |
| 4 | **emit_signal("x")与x.emit()混用** | 静态核对报「声明了信号但没人发」 | 核对脚本**两种写法都要认**;代码统一成x.emit()|
| 5 | **emit_signal(x)少引号** | 传了对象而非字符串,**运行时才炸** | 脚本报「改动 0 处」时,**先查模式是不是写错了** —— 本该命中的模式报 0 就是信号 |
| 6 | **-c内联脚本被 shell 吃转义** |
变真换行、引号被吞,**退出码仍是 0** | **含转义/引号的脚本一律写成.py文件再跑**,不用-c` |
9.5 验收:三类静态核对 + 一句诚实交代
目标平台未必装在你机器上(本例本机没有 Godot,所以「打开并运行」这一项
从未验证过)。能做的与必须说清的:
| 核验器 | 查什么 |
|---|---|
static_check.py |
内置类重名 / 跨脚本悬空引用 / 枚举 / 节点路径 / 资源存在 / 信号一致性 / 括号配对 |
| 题库核对 | 题量、字段、题型、pool↔right 同集合、字境名两处同源、单例注册 |
| 美术数据核对 | 道具数、场景数、色名都有分支、引用无悬空、无残留占位符 |
🔴 必须写进交付说明:没跑过就是没跑过。静态核对全绿不等于能跑。
静态核对抓不到的是逻辑接线错误(本例就抓到 2 个:卡片单选的点击没接上、
答完卡片整个消失)—— 那要能跑起来才看得见。
★ 核对脚本自己会误报 —— 本例一轮里误报 5 次(正则要求同行而生成器每字段一行 /
只认 .emit() 不认 emit_signal() / 没把内置构造法 new() 加白名单 /
派生色反解不到成了字符串 "None" / 把墙色名当成道具名)。
> 判据:正则与阈值型核对脚本,写完之后必须拿「已知合法项」跑一遍。
> 全红或全绿都要警惕 —— 可能是判据错,不是数据错。
> 报出问题时先问一句:这是数据的问题,还是我看的方式的问题?
> (与本技能第三节「批量替换大量 MISS」「断言硬编码会变的常量」同源。)
9.6 参考产物
| 参考产物 | 内容 |
|---|---|
| 外部 Godot 工程根(本机自有的 Godot 4 工程目录,不随本技能分发) | Godot 4 工程(8 字境 64 题 / 存档 / 25 套室内场景) |
同上 docs/PORTING_NOTES.md |
完整移植笔记(本节即由它提炼) |
同上类目下的 extract_*.js + convert_*.py |
三个机器转换器(题库 / 课文 / 室内数据) |
同上类目下的 static_check.py + check_*.py |
三个核验器,都可一键重跑 |
> ⚠️ 上表四行指的是本机另一个工程目录里的文件,不在本技能包内;
> 因此包内解析不到属正常现象。本表只说明「这套做法在那边已跑通」,不构成随包交付物。
9.7 两条会反复咬人的规律(本项目实测,务必先看)
规律一 · CRLF 会咬到你自己的产物,不只是外部源文件
第一次踩:H5 的 game.js 是纯 CRLF(2838 行 CRLF、0 裸 LF),按 \n 找锚点全落空。
第二次踩得更隐蔽:新建的 Godot 工程所有文件也都是 CRLF(继承了自己写文件的习惯),
于是每一个「按 \n 写模式串」的编辑脚本都会静默失配 ——
断言失败还算好的,坏的是替换 0 处也当成功,导致后面一连串莫名其妙的错。
处置(两条都做):
1. 把工程统一成 LF(一个脚本遍历转换,一次解决);
2. 编辑脚本读入即归一:t = t.replace('\r\n', '\n') ——
这样即使将来某个文件又变回 CRLF,脚本也不会静默失配。
规律二 · 静态核对的误报是连续发生的,不是偶然
本项目一轮里误报 10 次,逐条如下(这条演进史本身就是判据设计教材):
| # | 误报 | 根因 | 收窄方式 |
|---|---|---|---|
| 1 | order 题的 pool/right 全红 |
正则要求同行,而生成器每字段一行 | 正则允许 \s* |
| 2 | 「声明了信号但没人发」 | 只认 x.emit(),不认 emit_signal("x") |
两种写法都认 |
| 3 | .new() 判成悬空引用 |
没把内置构造法加白名单 | 白名单 |
| 4 | 道具的 lf/rt 全是 None |
派生色反解不到 → Python None → 字符串 "None" |
反解 shade |
| 5 | 25 套场景全「引用不存在的道具」 | 正则连 wall: ["WALL_HI",…] 也当道具名 |
只扫 props 段 |
| 6 | 字典键丢引号误报 69 处 | 函数参数表 func f(a: int) 与字典同形 |
跳过 func 行 |
| 7 | 同上仍误报 4 处 | 函数签名换行,续行不含 func |
跨行跟踪签名括号 |
| 8 | .get(unit_id, {}) 误报 8 处 |
unit_id 本来就是变量 |
先收集已声明标识符 |
| 9 | connect(func(): …) 报「方法不存在」 |
匿名函数不是方法名 | 跳过 func |
| 10 | signal x(a: int) 报「键没有引号」 |
信号声明的参数表同形 | 跳过 signal 行 |
> 判据(第 1~4 节已出现过,这里是加强版):
> 正则与阈值型核对脚本,写完之后必须拿「已知合法项」跑一遍。
> 全红 = 先怀疑判据;全绿 = 也要抽验两条。
> 报出问题时先问:这是数据的问题,还是我看的方式的问题?
>
> 修一次判据就要回头把之前所有已知合法项再跑一遍 ——
> 因为「收窄」与「放宽」是同一枚硬币:收窄 A 可能放宽 B。
9.9 最严重的一次事故:核心状态文件被静默丢失(务必看这条)
症状:项目的核心状态类丢了 12 个方法与全部字段,而所有静态核对一路报绿 ——
因为那套核对只查 类名.成员,查不出「同文件内的裸函数调用」。
那是一个一跑就全线崩的工程。
成因链(每步都合理,合起来是灾难):
1. 编辑脚本在第 4 个替换处断言失败 → 脚本中止;
2. 前面 3 个替换只在内存里做过,写盘在脚本末尾 ⇒ 前 3 步全白做;
3. 后来另一个脚本只补了存档段,丢失的字段与方法再没人补过。
两条硬教训:
> ① 编辑脚本要么「改一处写一处」,要么全做完再写但写完必须回读复核。
> 半步成功是最坏的结果 —— 它既不是「没改」也不是「改好了」。
>
> ② 每加一条静态核对,都要问一句:它能查出「调用了一个不存在的东西」吗?
> 只查跨文件引用远远不够。补上「跨全工程聚合的裸调用检查」之后,这类错才第一次可被抓到。
这条判据自己还误报了两次(累计误报已 13 次):首版按「本文件」判 → 把继承自引擎的
方法(queue_redraw/draw_rect/to_local/get_viewport)与信号声明当成未定义,
91 处误报;改跨全工程聚合后又漏滤运算关键字(not ( / or ()→ 8 处误报。
⇒ 规律不变:判据每收放一次都可能引入新误报,每次都要拿已知合法项复核。
9.8 阶段划分与「阶段性交付」的意义
本项目分四阶段,每一阶段都是可验收的完整切片,不是「写一半」:
| 阶段 | 切片 | 验收线 |
|---|---|---|
| 1 | 一个字境的完整闭环(地图→进境→作答→结算) | 能玩通一境 |
| 2 | 八个字境全通 + 存档 | 内容不缺、进度留得住 |
| 3 | 25 套室内场景 | 视觉资产全到位 |
| 4 | 残碑 / 祈愿灯 / 事件池 / 家长端 | 机制全到位 |
按「能玩通一条链路」切片,而不是按「系统」切 ——
按系统切会得到一堆各自不可验收的半成品;
按链路切,每阶段结束都是一个能演示、能让你否掉方向的东西。
还有一层:阶段 1 做完就锁定了三件事 —— 渲染方式、数据形态、核验器骨架。
后面三个阶段没有一次推翻前面的决定,全部是「填内容 + 加一条同类通道」。
⇒ 前期把架构岔路问清楚,比后期快跑重要得多。
下次再做同类移植,顺序:先量规模 → 定三个岔路(渲染方式 / 分期验收线 / 旧版去留)
→ 内容一律机器转换 → 每搬一块配一个核验器 → 核验器先拿合法项验一遍。