godofcong-1/erark/headless-game-test
Claude Code and agent skill by godofcong-1/erark
- community
Checking that the SKILL.md file is still in the repository.
在不启动图形界面的情况下无头运行 erArk:加载游戏配置、自建角色fixture做函数级单元测试,或加载指定存档、复刻某条指令、带护栏运行角色行为循环并逐轮内窥。用于验证新增的前提/结算器/效果链/口上是否生效,复现与定位死循环/卡死,以及修复前后的回归测试。当需要"给新增的前提或结算写单元测试"、"验证效果链顺序"、"复现某存档下的BUG"、"模拟执行某指令"、"验证行为循环收敛"、"排查NPC卡死"时使用。
Install
npx vskill@latest install godofcong-1/erark/headless-game-testSKILL.md
Skill Instructions
你是 erArk 的无头测试助手。本 skill 归纳了一套经实战验证的方法:不启动 Tk/Web 界面,在一个 Python 进程内完整初始化游戏、读取存档、复刻玩家指令、带护栏地运行行为循环,并对任意函数插桩观察每一轮的中间状态。
本 skill 覆盖两种模式,先选对模式再动手,选错会绕远路:
| 模式 | 什么时候用 | 模板 | 要不要读存档 | |
|---|---|---|---|---|
| A. 函数级单元测试 | 验证新增/改动的前提、结算器、效果链顺序、换装函数、口上是否生效 | unit_test_template.py | 不用,自建角色 fixture | |
| B. 行为循环诊断 | 复现存档里的 BUG、排查死循环/卡死、验证 AI 收敛 | harness_template.py | 要 |
两个模板都在本目录下,按其中 ==== 分段标记修改后即可直接运行。模式 A 请务必先读下面的「模式 A:函数级单元测试」一节——那里的 fixture 字段清单与两条全局前置是本 skill 里最容易反复踩坑的地方。
铁律(每条都是踩过的坑)
- 必须用 `./.conda/python.exe`,裸
python是 Windows 商店空壳别名(exit 49、无任何输出)。 - 外层必须加 `timeout N`(bash 的 timeout 命令)。游戏导入链(Flask/SocketIO 等)会启动非守护线程,测试跑完后进程也不会自行退出——
EXIT=124且结果已全部打印 = 正常结束,不是测试失败。也可在脚本末尾os._exit(0)主动退出。 - 加 `-u` 取消缓冲,且脚本开头
sys.stdout.reconfigure(encoding='utf-8', errors='replace')(Windows 控制台是 GBK,中文会乱码/报错)。 - 绝不用 `| tail` / `| head` 观察可能长时间运行的进程。管道会攒住全部输出直到进程结束,看起来和「卡死」一模一样——包括
buildconfig.py、tools/lint_target_csv.py这类跑几分钟的脚本,以及所有 harness。正确做法是重定向到文件再读:
``bash LOG="<scratchpad>/run.log" timeout 300 ./.conda/python.exe -u <脚本> > "$LOG" 2>&1; echo "EXIT=$?"; tail -30 "$LOG" ` ($TMPDIR 在 Git Bash 里是空的,> "$TMPDIR/x.log" 会写到 /x.log` 并 Permission denied,必须写完整的 scratchpad 路径。)
- 绝不调用会进入真实主循环的函数:
handle_instruct.handle_xxx()、chara_handle_instruct_common_settle()、update.game_update_flow()最终都会调init_character_behavior()。如果要排查的 BUG 正是死循环,harness 自己就会卡死。必须用"复刻指令赋值 + 手动推进时间 + 带护栏的自建循环"代替(见下文)。 - 只读纪律:读档只用
save_handle.input_load_save(id)(纯读入内存);绝不调用establish_save();不 inline 调用init_character_behavior()(其结尾的成就结算可能写文件)。所有修复先用 monkeypatch 在内存中模拟验证,确认收敛后再改源码。 - 含中文的脚本一律写成文件再跑,不要走 heredoc/stdin。Git Bash 把 heredoc 以 GBK 喂给 Python,脚本里的中文字符串会被解码成乱码,直接
SyntaxError: invalid syntax(报错行里能看到��就是这个原因)。正确做法:用 Write 工具把脚本写到 scratchpad(UTF-8),开头os.chdir(r"c:\code\erArk")+sys.path.insert(0, r"c:\code\erArk"),再timeout 300 ./.conda/python.exe -u "<scratchpad>/xxx.py" > "$LOG" 2>&1。纯 ASCII 的一次性小脚本才可以用 heredoc(./.conda/python.exe -u - <<'EOF',此时 sys.path[0] 为当前目录,可直接import auto_build_config)。同理,需要批量改含中文的 CSV/文档时也用脚本文件而不是 stdin。 - 测试脚本是回归资产,改完一轮功能就重跑上一轮的测试。同一子系统连续多个 plan 时(如 plan_14→17→18 的怀孕系统),每次实施后除了跑新测试,还要重跑前几轮留下的测试文件(scratchpad 会随会话丢失,长期要用的测试请在 plan 文档里记录其断言清单,或在用户同意后收进仓库)。
模式 A:函数级单元测试
不读存档、不跑行为循环,自建角色后直接调前提/结算器/换装函数做断言。模板:unit_test_template.py(可直接运行,自带 7 条示例断言)。
初始化链与模式 B 完全一样(见下一节),但额外有两条全局前置,漏了会在结算内部炸 KeyError,而且报错点离真正的原因很远:
fixture 字段与置零函数对照表
game_type.Character() 的多数容器字段初值是空 dict,而代码里普遍用 xxx[key] 直接下标访问,不预填就 KeyError。置零函数的命名和签名不规律,凭印象写必错,照抄下表:
| 字段 | 置零函数 | 坑 | |
|---|---|---|---|
cloth | get_cloth_zero() | 无参 | |
cloth.cloth_locker_in_dormitory | get_cloth_locker_in_dormitory_zero() | get_cloth_zero() 不含两个衣柜,要单独置零 | |
cloth.cloth_locker_in_shower | get_shower_cloth_locker_zero() | 命名顺序与上一行相反(shower_cloth_locker) | |
dirty | `get_zero_dirty()` | 不是 get_dirty_zero;另有 get_dirty_reset() 是别的语义 | |
talent | get_talent_zero({}) | 要传一个 dict 进去 | |
ability | get_ability_zero({}) | 同上 | |
status_data | `get_status_zero({})` | 字段叫 status_data,函数却叫 get_status_zero | |
juel | get_juel_zero({}) | 同上 | |
experience | get_experience_zero({}) | 同上 | |
second_behavior | get_second_behavior_zero({}) | 同上 | |
h_state | get_h_state_reset(cd.h_state) | 只有 reset 版,要把旧对象传进去 | |
body_manage | get_body_manage_zero() | 无参;管理类前提(如睡前锁门)会直接取 [24] | |
favorability | 手写 {0: 0} | 好感/信赖类前提直接取 [0];口上里的 favorability_ge_3 会走到 | |
talent[121] | 手写 cd.talent[121] = 1 | 罩杯素质 121~125 至少要有一个为 1:胸部成长/乳汁上限之类的代码用 for tid in [121..125]: if talent[tid]: ...; break 找罩杯,一个都没有时循环变量不赋值 → UnboundLocalError(报错在 chest_grow/milk_max 处) | |
race | 手写 cd.race = <种族id> | 默认 0=博士;生育方式、种族限定判定都查 game_config.config_race[cd.race],测卵生/多胎要显式给对应种族 | |
relationship / pregnancy | 默认即可 | Character() 已带完整默认对象;测亲子链时手写 relationship.mother_id/father_id/child_id_list 并两边都要挂(母亲与玩家的 child_id_list 都 append) | |
| 时间类字段 | cache.game_time - datetime.timedelta(days=N) | 造"N 天前"的 fertilization_time/born_time 用 timedelta 即可;但断言显示文本时要按 game_time.get_sub_date 的季月归并(只有 3/6/9/12 月)来预期,别硬算 t.month |
拿不准签名时先查再写,一次就能定:
断言写法
`add_time` 不要传 0:绝大多数结算器开头是 if not add_time: return,传 0 会静默什么都不做,断言变成假阴性。反过来,「add_time=0 时不生效」本身也值得单独断言一条。
更多经实战验证的断言样式:
可记录、可控序列的 randint 桩
需要验证「调用了几次随机、每次的参数范围、按序命中/不命中」时,用一个记录型桩替换 random.randint(模块里的 random 就是全局 random,改一处全局生效,用完必须还原):
按阈值探测概率:若第 i 轮的期望概率为 p_i,让桩在第 i 轮返回 int(p_i)(应命中)或 int(p_i)+1(应不命中),就能在不看内部变量的前提下断言"每轮概率确实等于公式值"。
面板 / 事件级测试(模式 A 扩展)
生育事件、送礼面板这类"带按钮循环 + 输入 + 创建角色 + 成就结算"的重流程也能在模式 A 里跑通,关键是绕开 Web 子面板包装、让按钮循环能退出、把需要完整罗德岛 fixture 的旁路打桩:
打桩的通用原则:from X import Y 进来的名字要打在使用它的模块上(born_event_panel.character_handle.xxx),改 character_handle.xxx 本体也行(同一模块对象),但 from Script.Design import character 后 character.input_name_func 这种模块属性就必须改模块属性本身。
新增数据后必须重建
改了 CSV(前提/效果/行为/target/口上)后先跑全量 `buildconfig.py` 再跑测试,否则断言读到的是旧生成物。新增口上文件还要先删 `data/Character_Talk.json`——auto_build_config.py 见它存在就跳过口上重建。
常见报错 → 病因速查
| 报错 | 病因 | ||
|---|---|---|---|
AttributeError: module 'attr_calculation' has no attribute 'get_xxx_zero' | 置零函数名记错,查上表 | ||
TypeError: get_xxx_zero() missing 1 required positional argument | 该置零函数要传 dict,查上表 | ||
KeyError 出现在 common_default.chara_base_state_adjust | 漏了 cache.all_system_setting | ||
KeyError: '0' 出现在 cache.scene_data[...] | 场景路径不是真实场景 | ||
KeyError: 24 出现在 handle_premise_body_manage | 漏了 body_manage | ||
KeyError: 0 出现在 handle_favorability_* | 漏了 favorability(口上前提会走到) | ||
| 断言全过但数值没变 | add_time 传了 0 | ||
| 新前提/新口上"没注册" | 漏跑 buildconfig.py,或新口上没删 Character_Talk.json | ||
| 进程"卡死"、看不到任何输出 | 用了 ` | tail` 管道,见铁律 4 | |
EXIT=124 但结果已打印完整 | 正常,见铁律 2 | ||
SyntaxError: invalid syntax,报错行里有 �� | heredoc/stdin 里的中文被 GBK 解码,改写成脚本文件,见铁律 7 | ||
UnboundLocalError 出现在 chest_grow / 乳汁上限 | fixture 没有罩杯素质,加 talent[121] = 1 | ||
KeyError: 1 出现在 achievement_panel.achievement_flow | 成就结算读 rhodes_island.materials_resouce,打桩 achievement_flow | ||
KeyError: 151 出现在 all_work_npc_set[151] | 成长文案读教育区教师集合,setdefault(151, set()) | ||
IndexError 出现在 random.choice(doctor_id_list) 之类 | 事件要随机选医生/干员,npc_id_got 为空或没有 profession=3 | ||
| 面板测试永不返回(无报错) | askfor_all 桩返回了不在 return_list 里的值,按钮循环退不出 | ||
调 get_new_character 后一连串 KeyError | 上线链要完整罗德岛 fixture,用只登记 npc_id_got 的 stub | ||
| 生成物里种族/道具名单与预期不符 | 用户可能在实施中手改了 CSV(本轮就遇到过追加种族),先 awk/grep 看实际 CSV 再改断言 |
模式 B:行为循环诊断
下面「第一步~第八步」是模式 B 的完整流程(模式 A 只需要第一步、第二步,再加上「模式 A」一节的两条全局前置与 fixture)。
第一步:初始化链(照抄 game.py 顺序,约 3 秒)
顺序不能乱:cache 先建、normal_config 先于 game_config、Script.Settle/Script.StateMachine 必须 import(装饰器注册)。Mod 系统可跳过(测试不需要)。
第二步:屏蔽 UI(防止任何绘制/等待阻塞)
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryChecking.github/skills/headless-game-test/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/godofcong-1/erark/headless-game-test)