用途
该技能面向单文件 C 游戏的回归测试。这类游戏是一个包含大量 static 声明的大型 .c 文件,外加一个存放 *_test.c 文件的目录,这些测试文件会被编译进游戏源码中。测试运行器通过 glob 匹配 *_test.c,因此把新的测试文件放入 source/ 目录即可自动加入测试套件。该模式适用于 Aeons Echo: Null Citadel 系列,同样适用于未来同类原生 C 游戏。
测试文件结构
测试文件应遵循以下形态:
#define WinMainCRTStartup WinMainDisabled /* silence WinMain from the included game */
#include "the_game.c" /* single TU: tests see every static */
#include <stdio.h>
#include "test_platform_stubs.inc" /* GL/window stubs */
static void reset_all(void){ memset(&g_save,0,sizeof(g_save));g_save.has_save=1;
init_player(1);clear_world();g_rng=; } /* fixed seed = deterministic */
static int check(const char*name,int ok){printf("%s=%s\n",name,ok?"PASS":"FAIL");return ok;}
int main(void){ int ok=1; ok&=my_feature_works(); ... return ok?0:1; }
编译规则
- 只编译测试文件本身。若同时加入 game.c,会产生大量多重定义错误(memset、_fltused、WinMainCRTStartup 以及每个 win32 导入)。
- 必须使用 -fno-builtin:游戏自定义了 memset/memcpy,缺少该标志时编译器会把它们变成自递归。
- 链接库放在源文件之后;添加 -Wl,--stack,8388608(用于大型静态数组)。
- 测试使用 mingw gcc 构建,即使发布构建使用 clang 严格模式;两条工具链都必须保持通过。
模拟世界夹具
- 空的 clear_world() 没有地板。任何发射投射物、掉落拾取物或期望爆炸落地的测试,都必须先 add_box() 一块地板(例如中心 v3(0,-.5,0),半尺寸 v3(60,.5,60))。
- 要测试地图填充,直接调用 build_<level>() 构建器——load_level() 会引入导演设置、音频和生成恢复,这些不适合单元测试。
- 测量原始受到伤害时,将玩家护甲归零(护甲会吸收约 65% 并造成误导)。
- 驱动 AI 时设置 state=AI_ALERT; alert=9; target=-2; last_seen=player; attack_cd=0,然后在有界循环中步进 update_entities(1/60)(绝不使用无界等待)。
调试卡住的模拟测试
在步进循环内部放置 printf 探针,而不是循环之后:每约 120 tick 打印一次,例如 printf("k=%d state=%d tele=%d burst=%d cd=%.2f fired=%d sees=%d dist=%.1f\n", ...)。状态数字会直接指向卡住的关卡(telegraph 从未打开 vs 已打开但从未提交 vs 投射物已生成但从未落地)。
项目特定细节(路径、现有辅助函数清单、存档布局断言、发布门禁)参见 references/native-c-game-harness-notes.md。
纪律
用户以 ponytail/full 模式运行此工作:在编写任何新内容之前,先 grep 令牌统计和现有辅助函数——本次会话中两次计划的功能实际上已经发布(无障碍滑块、结果屏幕)。通过现有模式实现最短差异者胜出。