miaoda-game-devkit 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/lint/gameplay-audit.contract.mjs +136 -13
- package/dist/lint/gameplay-contract.contract.mjs +2 -0
- package/dist/lint/vitest-config.contract.mjs +136 -21
- package/dist/vitest-config.js +134 -19
- package/dist/vitest-config.mjs +134 -19
- package/package.json +1 -28
package/README.md
CHANGED
|
@@ -17,10 +17,14 @@ Scene 命令、restart、业务 checkpoint 和销毁。玩法测试可以调用
|
|
|
17
17
|
`host.assertGameplayEvidence()` 把这些客观事实设为门禁;它不推断业务语义,
|
|
18
18
|
authoritative state 仍必须由测试显式断言。
|
|
19
19
|
|
|
20
|
-
玩法测试应优先使用 `gameplayTest`。它在 Vitest
|
|
21
|
-
在执行阶段绑定 HEADLESS host,并在 `onTestFinished` 校验 Ledger
|
|
22
|
-
|
|
23
|
-
Scene
|
|
20
|
+
玩法测试应优先使用 `gameplayTest`。它在 Vitest 收集阶段写入测试声明,
|
|
21
|
+
在执行阶段绑定 HEADLESS host,并在 `onTestFinished` 校验 Ledger。配置加载阶段会先校验
|
|
22
|
+
交互项目的最低基线:至少一个 `kind: "playthrough"` contract 必须要求真实输入、完整帧和
|
|
23
|
+
语义 checkpoint,每个非入口 Scene 必须有生产转场覆盖;检测到 `scene.restart()` 时还必须
|
|
24
|
+
声明 `flows.restart: true` 和 `kind: "recovery"` contract。配置预检失败时测试和 coverage
|
|
25
|
+
都不会运行。配置通过后,`gameplayAudit` reporter 才会检查测试声明,并在结束时汇总
|
|
26
|
+
缺失、失败、证据不足、Scene 未访问和 registry 不一致。`registerGameplayContract` 保留为
|
|
27
|
+
不参加执行前预检的低层接口。
|
|
24
28
|
|
|
25
29
|
模板直接调用开发工具包提供的命令行程序:
|
|
26
30
|
|
|
@@ -83,7 +87,12 @@ DOM 到 Phaser 的输入、命中测试和游戏状态变化,不验证 Canvas/
|
|
|
83
87
|
断言 authoritative state 后调用 `host.checkpoint("progress")`、
|
|
84
88
|
`host.checkpoint("recovery")` 等稳定检查点,并在清单中通过 `requireCheckpoint` 声明。
|
|
85
89
|
运行结束会输出稳定的 `GAMEPLAY_AUDIT: ALL CONTRACTS PASSED` 或
|
|
86
|
-
`GAMEPLAY_AUDIT: ... FAILED`
|
|
90
|
+
`GAMEPLAY_AUDIT: ... FAILED` 标识。该标识只表示玩法证据审计;`pnpm test` 的整体结果
|
|
91
|
+
必须以最后的 `TEST_RESULT: PASS` 或 `TEST_RESULT: FAIL` 为准。失败报告会合并重复的问题,
|
|
92
|
+
并给出按类别去重的修复建议,先处理列出的原始测试错误,再重新运行 `pnpm test`。
|
|
93
|
+
|
|
94
|
+
共享 Vitest 配置使用 `minimal` reporter 和 `text-summary` coverage reporter,保留失败
|
|
95
|
+
定位、Scene 覆盖摘要和最终机器标识,同时避免输出完整测试列表和逐文件噪声。
|
|
87
96
|
|
|
88
97
|
## 发布顺序
|
|
89
98
|
|
|
@@ -217,7 +217,7 @@ function auditGameplayCollection(options2, file, tests) {
|
|
|
217
217
|
const matches = actual.filter((test) => test.metadata?.id === contract.id);
|
|
218
218
|
if (matches.length === 0) {
|
|
219
219
|
issues.push(
|
|
220
|
-
`\
|
|
220
|
+
`\u6CA1\u6709\u627E\u5230\u68C0\u67E5\u9879 "${contract.id}" \u7684\u53EF\u6267\u884C\u6D4B\u8BD5\uFF08${normalizedFile}\uFF09\u3002`
|
|
221
221
|
);
|
|
222
222
|
} else if (matches.length > 1) {
|
|
223
223
|
issues.push(
|
|
@@ -248,12 +248,15 @@ function auditGameplayCollection(options2, file, tests) {
|
|
|
248
248
|
function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
249
249
|
const issues = [];
|
|
250
250
|
const inputEvents = evidence.mouseEvents + evidence.keyboardEvents + evidence.touchEvents;
|
|
251
|
-
if (requirements.requireInput && inputEvents === 0)
|
|
251
|
+
if (requirements.requireInput && inputEvents === 0) {
|
|
252
252
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165\u3002`);
|
|
253
|
-
|
|
253
|
+
}
|
|
254
|
+
if (requirements.requireFrameAdvance && evidence.frames === 0) {
|
|
254
255
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27\u3002`);
|
|
255
|
-
|
|
256
|
+
}
|
|
257
|
+
if (requirements.requirePhysicsStep && evidence.physicsSteps === 0) {
|
|
256
258
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB Arcade Physics\u3002`);
|
|
259
|
+
}
|
|
257
260
|
for (const target of normalizeList(requirements.requireTransition)) {
|
|
258
261
|
if (!evidence.transitions.some((transition) => transition.to === target)) {
|
|
259
262
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u5230 ${target} \u7684 Scene \u8F6C\u573A\u3002`);
|
|
@@ -264,21 +267,24 @@ function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
|
264
267
|
issues.push(`contract "${id}" \u672A\u8BBF\u95EE Scene "${scene}"\u3002`);
|
|
265
268
|
}
|
|
266
269
|
for (const scene of normalizeList(requirements.requireRestart)) {
|
|
267
|
-
if (!evidence.restartedScenes.includes(scene))
|
|
270
|
+
if (!evidence.restartedScenes.includes(scene)) {
|
|
268
271
|
issues.push(`contract "${id}" \u672A\u91CD\u542F Scene "${scene}"\u3002`);
|
|
272
|
+
}
|
|
269
273
|
}
|
|
270
274
|
for (const checkpoint of normalizeList(requirements.requireCheckpoint)) {
|
|
271
|
-
if (!evidence.checkpoints.includes(checkpoint))
|
|
275
|
+
if (!evidence.checkpoints.includes(checkpoint)) {
|
|
272
276
|
issues.push(`contract "${id}" \u7F3A\u5C11 checkpoint "${checkpoint}"\u3002`);
|
|
277
|
+
}
|
|
273
278
|
}
|
|
274
|
-
if (requirements.requireDestroy && !evidence.destroyed)
|
|
279
|
+
if (requirements.requireDestroy && !evidence.destroyed) {
|
|
275
280
|
issues.push(`contract "${id}" \u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1\u3002`);
|
|
281
|
+
}
|
|
276
282
|
return issues;
|
|
277
283
|
}
|
|
278
284
|
function auditGameplayRun(options2, tests) {
|
|
279
285
|
const issues = [];
|
|
280
286
|
const actualContracts = tests.filter((test) => test.metadata);
|
|
281
|
-
const
|
|
287
|
+
const observedEvidence = [];
|
|
282
288
|
for (const expected of options2.contracts) {
|
|
283
289
|
const matches = actualContracts.filter(
|
|
284
290
|
(test2) => test2.metadata?.id === expected.id
|
|
@@ -294,6 +300,7 @@ function auditGameplayRun(options2, tests) {
|
|
|
294
300
|
const test = matches[0];
|
|
295
301
|
const metadata = test?.metadata;
|
|
296
302
|
if (!test || !metadata) continue;
|
|
303
|
+
if (metadata.evidence) observedEvidence.push(metadata.evidence);
|
|
297
304
|
if (test.file.replaceAll("\\", "/") !== expected.testFile.replaceAll("\\", "/")) {
|
|
298
305
|
issues.push(
|
|
299
306
|
`contract "${expected.id}" \u5FC5\u987B\u4F4D\u4E8E ${expected.testFile}\uFF0C\u5B9E\u9645\u4F4D\u4E8E ${test.file}\u3002`
|
|
@@ -314,7 +321,6 @@ function auditGameplayRun(options2, tests) {
|
|
|
314
321
|
metadata.evidence
|
|
315
322
|
);
|
|
316
323
|
issues.push(...evidenceIssues);
|
|
317
|
-
if (evidenceIssues.length === 0) acceptedEvidence.push(metadata.evidence);
|
|
318
324
|
}
|
|
319
325
|
for (const test of actualContracts) {
|
|
320
326
|
const id = test.metadata?.id;
|
|
@@ -325,14 +331,15 @@ function auditGameplayRun(options2, tests) {
|
|
|
325
331
|
}
|
|
326
332
|
}
|
|
327
333
|
const registeredScenes = normalizeSet(
|
|
328
|
-
|
|
334
|
+
observedEvidence.flatMap((evidence) => evidence.registeredScenes)
|
|
329
335
|
);
|
|
330
336
|
const expectedScenes = normalizeSet(options2.scenes);
|
|
331
337
|
for (const scene of expectedScenes) {
|
|
332
|
-
if (!registeredScenes.includes(scene))
|
|
338
|
+
if (!registeredScenes.includes(scene)) {
|
|
333
339
|
issues.push(
|
|
334
|
-
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\
|
|
340
|
+
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry \u4E2D\u3002`
|
|
335
341
|
);
|
|
342
|
+
}
|
|
336
343
|
}
|
|
337
344
|
for (const scene of registeredScenes) {
|
|
338
345
|
if (!expectedScenes.includes(scene))
|
|
@@ -343,6 +350,81 @@ function auditGameplayRun(options2, tests) {
|
|
|
343
350
|
return { passed: issues.length === 0, issues: [...new Set(issues)] };
|
|
344
351
|
}
|
|
345
352
|
|
|
353
|
+
// src/gameplay-audit-reporter.ts
|
|
354
|
+
import { relative } from "path";
|
|
355
|
+
function formatGameplayIssues(issues) {
|
|
356
|
+
const groups = /* @__PURE__ */ new Map();
|
|
357
|
+
const repairKeys = /* @__PURE__ */ new Set();
|
|
358
|
+
const repairs = [];
|
|
359
|
+
const repairText = {
|
|
360
|
+
input: "\u8F93\u5165\uFF1A\u5148\u63A8\u8FDB\u4E00\u5E27\uFF0C\u518D\u901A\u8FC7 host.input.mouse\u3001host.input.keyboard \u6216 host.input.touch \u9A71\u52A8\u771F\u5B9E\u751F\u4EA7\u8F93\u5165\u3002",
|
|
361
|
+
frames: "\u5E27\u63A8\u8FDB\uFF1A\u5728\u8F93\u5165\u6216\u751F\u4EA7\u547D\u4EE4\u540E\u8C03\u7528 host.stepFrames()\uFF0C\u5F02\u6B65 update \u4F7F\u7528 host.stepFramesAsync()\u3002",
|
|
362
|
+
physics: "\u7269\u7406\uFF1A\u4EC5\u5728\u751F\u4EA7\u4F7F\u7528 Arcade Physics \u65F6\u8C03\u7528 host.stepPhysics()\uFF1B\u5176\u4ED6\u7269\u7406\u7CFB\u7EDF\u4F7F\u7528\u5B8C\u6574\u5E27\u3002",
|
|
363
|
+
transition: "\u8F6C\u573A\uFF1Aboot \u524D\u6CE8\u518C\u76EE\u6807 Scene\uFF0C\u8C03\u7528\u751F\u4EA7\u8F6C\u573A\u547D\u4EE4\uFF0C\u518D\u7528 host.stepUntil() \u7B49\u5F85\u76EE\u6807\u6FC0\u6D3B\u5E76\u65AD\u8A00\u6E90/\u76EE\u6807\u72B6\u6001\u3002",
|
|
364
|
+
visit: "Scene\uFF1A\u786E\u8BA4\u6D4B\u8BD5\u901A\u8FC7\u771F\u5B9E\u5165\u53E3\u6216\u5408\u6CD5\u8F6C\u573A\u8BBF\u95EE\u8BE5 Scene\uFF0C\u5E76\u4F7F\u7528\u771F\u5B9E\u914D\u7F6E\u542F\u52A8\u3002",
|
|
365
|
+
restart: "\u6062\u590D\uFF1A\u901A\u8FC7\u771F\u5B9E\u8F93\u5165\u6216\u751F\u4EA7 controls \u89E6\u53D1 restart\uFF0C\u7B49\u5F85\u5B8C\u6574\u5E27\u5E76\u65AD\u8A00\u72B6\u6001\u5DF2\u6062\u590D\u3002",
|
|
366
|
+
checkpoint: "\u68C0\u67E5\u70B9\uFF1A\u5148\u65AD\u8A00 authoritative state\uFF0C\u518D\u8C03\u7528 host.checkpoint()\uFF1B\u4E0D\u8981\u8C03\u7528 contract.checkpoint()\u3002",
|
|
367
|
+
destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
|
|
368
|
+
registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4 Scene \u5728\u751F\u4EA7 registry \u4E2D\uFF0C\u5E76\u5728 boot \u524D\u901A\u8FC7 additionalScenes \u6CE8\u518C\u8F6C\u573A\u76EE\u6807\u3002",
|
|
369
|
+
missing: "\u6D4B\u8BD5\u7F3A\u5931\uFF1A\u5728\u6307\u5B9A\u6587\u4EF6\u4F7F\u7528 gameplayTest \u5B9A\u4E49\u68C0\u67E5\u9879\uFF0C\u5E76\u901A\u8FC7 contract.attachHost(host) \u7ED1\u5B9A host\u3002",
|
|
370
|
+
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF1B\u68C0\u67E5\u9879\u53EA\u6C47\u603B\u7ED3\u679C\uFF0C\u4E0D\u662F\u8FD0\u884C\u65F6\u5BF9\u8C61\u3002"
|
|
371
|
+
};
|
|
372
|
+
const getCategory = (issue) => {
|
|
373
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
374
|
+
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
375
|
+
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics")) return { category: "physics", key: "physics" };
|
|
376
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u5230 ") && issue.includes("Scene \u8F6C\u573A")) {
|
|
377
|
+
const target = issue.match(/未记录到 (.+?) 的 Scene 转场/)?.[1] ?? "unknown";
|
|
378
|
+
return { category: "transition", key: `transition:${target}` };
|
|
379
|
+
}
|
|
380
|
+
if (issue.includes("\u672A\u8BBF\u95EE Scene")) {
|
|
381
|
+
const scene = issue.match(/未访问 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
382
|
+
return { category: "visit", key: `visit:${scene}` };
|
|
383
|
+
}
|
|
384
|
+
if (issue.includes("\u672A\u91CD\u542F Scene")) {
|
|
385
|
+
const scene = issue.match(/未重启 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
386
|
+
return { category: "restart", key: `restart:${scene}` };
|
|
387
|
+
}
|
|
388
|
+
if (issue.includes("\u7F3A\u5C11 checkpoint")) return { category: "checkpoint", key: `checkpoint:${issue.match(/checkpoint "([^"]+)"/)?.[1] ?? issue}` };
|
|
389
|
+
if (issue.includes("\u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1")) return { category: "destroy", key: "destroy" };
|
|
390
|
+
if (issue.includes("\u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry")) {
|
|
391
|
+
const scene = issue.match(/生产 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
392
|
+
return { category: "registry", key: `registry:${scene}` };
|
|
393
|
+
}
|
|
394
|
+
if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
|
|
395
|
+
if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
|
|
396
|
+
return { category: "other", key: `other:${issue}` };
|
|
397
|
+
};
|
|
398
|
+
for (const rawIssue of [...new Set(issues)]) {
|
|
399
|
+
const compact = rawIssue.replaceAll(/\s+/g, " ").trim();
|
|
400
|
+
const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u68C0\u67E5\u9879 "$1"').replaceAll("evidence requirements", "\u6240\u9700\u8FD0\u884C\u8BC1\u636E").replaceAll("metadata", "\u6D4B\u8BD5\u58F0\u660E").replaceAll("host registry", "\u5DF2\u6CE8\u518C Scene \u6E05\u5355");
|
|
401
|
+
const id = compact.match(/contract "([^"]+)"/)?.[1];
|
|
402
|
+
const { category, key } = getCategory(compact);
|
|
403
|
+
const group = groups.get(key);
|
|
404
|
+
if (group) {
|
|
405
|
+
if (id && !group.ids.includes(id)) group.ids.push(id);
|
|
406
|
+
} else {
|
|
407
|
+
groups.set(key, { text: readable, ids: id ? [id] : [], key });
|
|
408
|
+
}
|
|
409
|
+
if (repairText[category] && !repairKeys.has(category)) {
|
|
410
|
+
repairKeys.add(category);
|
|
411
|
+
repairs.push(repairText[category]);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const grouped = [...groups.values()];
|
|
415
|
+
const visible = grouped.slice(0, 8).map((group) => {
|
|
416
|
+
if (group.ids.length <= 1) return group.text;
|
|
417
|
+
return `${group.text}\uFF08\u53D7\u5F71\u54CD\u68C0\u67E5\u9879\uFF1A${group.ids.join("\u3001")}\uFF09`;
|
|
418
|
+
});
|
|
419
|
+
return {
|
|
420
|
+
issues: visible.map(
|
|
421
|
+
(issue) => issue.length > 360 ? `${issue.slice(0, 357)}...` : issue
|
|
422
|
+
),
|
|
423
|
+
repairs: repairs.slice(0, 8),
|
|
424
|
+
omitted: Math.max(0, grouped.length - visible.length)
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
346
428
|
// src/lint/gameplay-audit.test.ts
|
|
347
429
|
var options = {
|
|
348
430
|
mode: "interactive",
|
|
@@ -568,7 +650,7 @@ describe("gameplay audit", () => {
|
|
|
568
650
|
expect(result).toEqual({
|
|
569
651
|
passed: false,
|
|
570
652
|
issues: [
|
|
571
|
-
'\
|
|
653
|
+
'\u6CA1\u6709\u627E\u5230\u68C0\u67E5\u9879 "gameplay.main-flow" \u7684\u53EF\u6267\u884C\u6D4B\u8BD5\uFF08tests/gameplay.test.ts\uFF09\u3002'
|
|
572
654
|
]
|
|
573
655
|
});
|
|
574
656
|
});
|
|
@@ -602,6 +684,7 @@ describe("gameplay audit", () => {
|
|
|
602
684
|
evidence.destroyed = false;
|
|
603
685
|
evidence.registeredScenes.push("DebugScene");
|
|
604
686
|
const test = createPassingTest();
|
|
687
|
+
if (!test.metadata) throw new Error("expected gameplay metadata");
|
|
605
688
|
test.metadata.evidence = evidence;
|
|
606
689
|
const result = auditGameplayRun(options, [test]);
|
|
607
690
|
expect(result.passed).toBe(false);
|
|
@@ -613,8 +696,24 @@ describe("gameplay audit", () => {
|
|
|
613
696
|
])
|
|
614
697
|
);
|
|
615
698
|
});
|
|
699
|
+
it("\u4FDD\u7559\u5931\u8D25 host \u5DF2\u6CE8\u518C\u7684 Scene\uFF0C\u907F\u514D checkpoint \u7F3A\u53E3\u7EA7\u8054\u6210\u6CE8\u518C\u9519\u8BEF", () => {
|
|
700
|
+
const test = createPassingTest();
|
|
701
|
+
if (!test.metadata?.evidence) throw new Error("expected gameplay evidence");
|
|
702
|
+
test.metadata.evidence.checkpoints = ["progress"];
|
|
703
|
+
const result = auditGameplayRun(options, [test]);
|
|
704
|
+
expect(result.issues).toContain(
|
|
705
|
+
'contract "gameplay.main-flow" \u7F3A\u5C11 checkpoint "recovery"\u3002'
|
|
706
|
+
);
|
|
707
|
+
expect(result.issues).not.toContain(
|
|
708
|
+
'\u751F\u4EA7 Scene "IndexScene" \u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry \u4E2D\u3002'
|
|
709
|
+
);
|
|
710
|
+
expect(result.issues).not.toContain(
|
|
711
|
+
'\u751F\u4EA7 Scene "BattleScene" \u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry \u4E2D\u3002'
|
|
712
|
+
);
|
|
713
|
+
});
|
|
616
714
|
it("\u62D2\u7EDD\u901A\u8FC7\u5951\u7EA6\u7684 host registry \u4E2D\u51FA\u73B0\u6E05\u5355\u5916 Scene", () => {
|
|
617
715
|
const test = createPassingTest();
|
|
716
|
+
if (!test.metadata?.evidence) throw new Error("expected gameplay evidence");
|
|
618
717
|
test.metadata.evidence.registeredScenes.push("DebugScene");
|
|
619
718
|
expect(auditGameplayRun(options, [test]).issues).toContain(
|
|
620
719
|
'host registry \u51FA\u73B0\u672A\u5217\u5165 gameplayAudit.scenes \u7684\u751F\u4EA7 Scene "DebugScene"\u3002'
|
|
@@ -647,4 +746,28 @@ describe("gameplay audit", () => {
|
|
|
647
746
|
)
|
|
648
747
|
).toContain('contract "arcade-physics" \u672A\u63A8\u8FDB Arcade Physics\u3002');
|
|
649
748
|
});
|
|
749
|
+
it("\u5408\u5E76\u76F8\u540C\u4FEE\u590D\u7C7B\u578B\u4F46\u4FDD\u7559\u4E0D\u540C Scene \u8F6C\u573A\u76EE\u6807", () => {
|
|
750
|
+
const result = formatGameplayIssues([
|
|
751
|
+
'contract "Menu.start" \u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165\u3002',
|
|
752
|
+
'contract "Battle.start" \u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165\u3002',
|
|
753
|
+
'contract "Menu.to-battle" \u672A\u8BB0\u5F55\u5230 BattleScene \u7684 Scene \u8F6C\u573A\u3002',
|
|
754
|
+
'contract "Battle.to-result" \u672A\u8BB0\u5F55\u5230 ResultScene \u7684 Scene \u8F6C\u573A\u3002',
|
|
755
|
+
'contract "Menu.visit-result" \u672A\u8BBF\u95EE Scene "ResultScene"\u3002',
|
|
756
|
+
'contract "Battle.visit-result" \u672A\u8BBF\u95EE Scene "ResultScene"\u3002'
|
|
757
|
+
]);
|
|
758
|
+
expect(result.issues).toHaveLength(4);
|
|
759
|
+
expect(result.repairs).toHaveLength(3);
|
|
760
|
+
expect(result.repairs[0]).toContain("\u8F93\u5165");
|
|
761
|
+
expect(result.repairs[1]).toContain("\u8F6C\u573A");
|
|
762
|
+
});
|
|
763
|
+
it("\u5C06\u591A\u68C0\u67E5\u9879\u7684\u7F3A\u53E3\u9650\u5236\u4E3A\u95EE\u9898\u7EC4\u800C\u4E0D\u662F\u539F\u59CB\u65E5\u5FD7\u884C", () => {
|
|
764
|
+
const issues = Array.from(
|
|
765
|
+
{ length: 20 },
|
|
766
|
+
(_, index) => `contract "Scene${index}.boot" \u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165\u3002`
|
|
767
|
+
);
|
|
768
|
+
const result = formatGameplayIssues(issues);
|
|
769
|
+
expect(result.issues).toHaveLength(1);
|
|
770
|
+
expect(result.repairs).toHaveLength(1);
|
|
771
|
+
expect(result.omitted).toBe(0);
|
|
772
|
+
});
|
|
650
773
|
});
|
|
@@ -1188,6 +1188,7 @@ describe("gameplay contract lifecycle integration", () => {
|
|
|
1188
1188
|
const { callbacks, context } = createTestContext();
|
|
1189
1189
|
const contract = registerGameplayContract(context, {
|
|
1190
1190
|
id: "InputScene.hit",
|
|
1191
|
+
kind: "playthrough",
|
|
1191
1192
|
requireInput: true,
|
|
1192
1193
|
requireFrameAdvance: true
|
|
1193
1194
|
});
|
|
@@ -1216,6 +1217,7 @@ describe("gameplay contract lifecycle integration", () => {
|
|
|
1216
1217
|
const { callbacks, context } = createTestContext();
|
|
1217
1218
|
const contract = registerGameplayContract(context, {
|
|
1218
1219
|
id: "InputScene.missing-input",
|
|
1220
|
+
kind: "playthrough",
|
|
1219
1221
|
requireInput: true
|
|
1220
1222
|
});
|
|
1221
1223
|
const host = await createHeadlessGame(new InputScene());
|
|
@@ -222,7 +222,7 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
222
222
|
const matches = actual.filter((test) => test.metadata?.id === contract.id);
|
|
223
223
|
if (matches.length === 0) {
|
|
224
224
|
issues.push(
|
|
225
|
-
`\
|
|
225
|
+
`\u6CA1\u6709\u627E\u5230\u68C0\u67E5\u9879 "${contract.id}" \u7684\u53EF\u6267\u884C\u6D4B\u8BD5\uFF08${normalizedFile}\uFF09\u3002`
|
|
226
226
|
);
|
|
227
227
|
} else if (matches.length > 1) {
|
|
228
228
|
issues.push(
|
|
@@ -253,12 +253,15 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
253
253
|
function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
254
254
|
const issues = [];
|
|
255
255
|
const inputEvents = evidence.mouseEvents + evidence.keyboardEvents + evidence.touchEvents;
|
|
256
|
-
if (requirements.requireInput && inputEvents === 0)
|
|
256
|
+
if (requirements.requireInput && inputEvents === 0) {
|
|
257
257
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165\u3002`);
|
|
258
|
-
|
|
258
|
+
}
|
|
259
|
+
if (requirements.requireFrameAdvance && evidence.frames === 0) {
|
|
259
260
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27\u3002`);
|
|
260
|
-
|
|
261
|
+
}
|
|
262
|
+
if (requirements.requirePhysicsStep && evidence.physicsSteps === 0) {
|
|
261
263
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB Arcade Physics\u3002`);
|
|
264
|
+
}
|
|
262
265
|
for (const target of normalizeList(requirements.requireTransition)) {
|
|
263
266
|
if (!evidence.transitions.some((transition) => transition.to === target)) {
|
|
264
267
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u5230 ${target} \u7684 Scene \u8F6C\u573A\u3002`);
|
|
@@ -269,21 +272,24 @@ function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
|
269
272
|
issues.push(`contract "${id}" \u672A\u8BBF\u95EE Scene "${scene}"\u3002`);
|
|
270
273
|
}
|
|
271
274
|
for (const scene of normalizeList(requirements.requireRestart)) {
|
|
272
|
-
if (!evidence.restartedScenes.includes(scene))
|
|
275
|
+
if (!evidence.restartedScenes.includes(scene)) {
|
|
273
276
|
issues.push(`contract "${id}" \u672A\u91CD\u542F Scene "${scene}"\u3002`);
|
|
277
|
+
}
|
|
274
278
|
}
|
|
275
279
|
for (const checkpoint of normalizeList(requirements.requireCheckpoint)) {
|
|
276
|
-
if (!evidence.checkpoints.includes(checkpoint))
|
|
280
|
+
if (!evidence.checkpoints.includes(checkpoint)) {
|
|
277
281
|
issues.push(`contract "${id}" \u7F3A\u5C11 checkpoint "${checkpoint}"\u3002`);
|
|
282
|
+
}
|
|
278
283
|
}
|
|
279
|
-
if (requirements.requireDestroy && !evidence.destroyed)
|
|
284
|
+
if (requirements.requireDestroy && !evidence.destroyed) {
|
|
280
285
|
issues.push(`contract "${id}" \u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1\u3002`);
|
|
286
|
+
}
|
|
281
287
|
return issues;
|
|
282
288
|
}
|
|
283
289
|
function auditGameplayRun(options, tests) {
|
|
284
290
|
const issues = [];
|
|
285
291
|
const actualContracts = tests.filter((test) => test.metadata);
|
|
286
|
-
const
|
|
292
|
+
const observedEvidence = [];
|
|
287
293
|
for (const expected of options.contracts) {
|
|
288
294
|
const matches = actualContracts.filter(
|
|
289
295
|
(test2) => test2.metadata?.id === expected.id
|
|
@@ -299,6 +305,7 @@ function auditGameplayRun(options, tests) {
|
|
|
299
305
|
const test = matches[0];
|
|
300
306
|
const metadata = test?.metadata;
|
|
301
307
|
if (!test || !metadata) continue;
|
|
308
|
+
if (metadata.evidence) observedEvidence.push(metadata.evidence);
|
|
302
309
|
if (test.file.replaceAll("\\", "/") !== expected.testFile.replaceAll("\\", "/")) {
|
|
303
310
|
issues.push(
|
|
304
311
|
`contract "${expected.id}" \u5FC5\u987B\u4F4D\u4E8E ${expected.testFile}\uFF0C\u5B9E\u9645\u4F4D\u4E8E ${test.file}\u3002`
|
|
@@ -319,7 +326,6 @@ function auditGameplayRun(options, tests) {
|
|
|
319
326
|
metadata.evidence
|
|
320
327
|
);
|
|
321
328
|
issues.push(...evidenceIssues);
|
|
322
|
-
if (evidenceIssues.length === 0) acceptedEvidence.push(metadata.evidence);
|
|
323
329
|
}
|
|
324
330
|
for (const test of actualContracts) {
|
|
325
331
|
const id = test.metadata?.id;
|
|
@@ -330,14 +336,15 @@ function auditGameplayRun(options, tests) {
|
|
|
330
336
|
}
|
|
331
337
|
}
|
|
332
338
|
const registeredScenes = normalizeSet(
|
|
333
|
-
|
|
339
|
+
observedEvidence.flatMap((evidence) => evidence.registeredScenes)
|
|
334
340
|
);
|
|
335
341
|
const expectedScenes = normalizeSet(options.scenes);
|
|
336
342
|
for (const scene of expectedScenes) {
|
|
337
|
-
if (!registeredScenes.includes(scene))
|
|
343
|
+
if (!registeredScenes.includes(scene)) {
|
|
338
344
|
issues.push(
|
|
339
|
-
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\
|
|
345
|
+
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry \u4E2D\u3002`
|
|
340
346
|
);
|
|
347
|
+
}
|
|
341
348
|
}
|
|
342
349
|
for (const scene of registeredScenes) {
|
|
343
350
|
if (!expectedScenes.includes(scene))
|
|
@@ -367,11 +374,96 @@ function toAuditTest(test) {
|
|
|
367
374
|
metadata: isGameplayContractMetadata(metadata) ? metadata : void 0
|
|
368
375
|
};
|
|
369
376
|
}
|
|
377
|
+
function formatGameplayIssues(issues) {
|
|
378
|
+
const groups = /* @__PURE__ */ new Map();
|
|
379
|
+
const repairKeys = /* @__PURE__ */ new Set();
|
|
380
|
+
const repairs = [];
|
|
381
|
+
const repairText = {
|
|
382
|
+
input: "\u8F93\u5165\uFF1A\u5148\u63A8\u8FDB\u4E00\u5E27\uFF0C\u518D\u901A\u8FC7 host.input.mouse\u3001host.input.keyboard \u6216 host.input.touch \u9A71\u52A8\u771F\u5B9E\u751F\u4EA7\u8F93\u5165\u3002",
|
|
383
|
+
frames: "\u5E27\u63A8\u8FDB\uFF1A\u5728\u8F93\u5165\u6216\u751F\u4EA7\u547D\u4EE4\u540E\u8C03\u7528 host.stepFrames()\uFF0C\u5F02\u6B65 update \u4F7F\u7528 host.stepFramesAsync()\u3002",
|
|
384
|
+
physics: "\u7269\u7406\uFF1A\u4EC5\u5728\u751F\u4EA7\u4F7F\u7528 Arcade Physics \u65F6\u8C03\u7528 host.stepPhysics()\uFF1B\u5176\u4ED6\u7269\u7406\u7CFB\u7EDF\u4F7F\u7528\u5B8C\u6574\u5E27\u3002",
|
|
385
|
+
transition: "\u8F6C\u573A\uFF1Aboot \u524D\u6CE8\u518C\u76EE\u6807 Scene\uFF0C\u8C03\u7528\u751F\u4EA7\u8F6C\u573A\u547D\u4EE4\uFF0C\u518D\u7528 host.stepUntil() \u7B49\u5F85\u76EE\u6807\u6FC0\u6D3B\u5E76\u65AD\u8A00\u6E90/\u76EE\u6807\u72B6\u6001\u3002",
|
|
386
|
+
visit: "Scene\uFF1A\u786E\u8BA4\u6D4B\u8BD5\u901A\u8FC7\u771F\u5B9E\u5165\u53E3\u6216\u5408\u6CD5\u8F6C\u573A\u8BBF\u95EE\u8BE5 Scene\uFF0C\u5E76\u4F7F\u7528\u771F\u5B9E\u914D\u7F6E\u542F\u52A8\u3002",
|
|
387
|
+
restart: "\u6062\u590D\uFF1A\u901A\u8FC7\u771F\u5B9E\u8F93\u5165\u6216\u751F\u4EA7 controls \u89E6\u53D1 restart\uFF0C\u7B49\u5F85\u5B8C\u6574\u5E27\u5E76\u65AD\u8A00\u72B6\u6001\u5DF2\u6062\u590D\u3002",
|
|
388
|
+
checkpoint: "\u68C0\u67E5\u70B9\uFF1A\u5148\u65AD\u8A00 authoritative state\uFF0C\u518D\u8C03\u7528 host.checkpoint()\uFF1B\u4E0D\u8981\u8C03\u7528 contract.checkpoint()\u3002",
|
|
389
|
+
destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
|
|
390
|
+
registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4 Scene \u5728\u751F\u4EA7 registry \u4E2D\uFF0C\u5E76\u5728 boot \u524D\u901A\u8FC7 additionalScenes \u6CE8\u518C\u8F6C\u573A\u76EE\u6807\u3002",
|
|
391
|
+
missing: "\u6D4B\u8BD5\u7F3A\u5931\uFF1A\u5728\u6307\u5B9A\u6587\u4EF6\u4F7F\u7528 gameplayTest \u5B9A\u4E49\u68C0\u67E5\u9879\uFF0C\u5E76\u901A\u8FC7 contract.attachHost(host) \u7ED1\u5B9A host\u3002",
|
|
392
|
+
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF1B\u68C0\u67E5\u9879\u53EA\u6C47\u603B\u7ED3\u679C\uFF0C\u4E0D\u662F\u8FD0\u884C\u65F6\u5BF9\u8C61\u3002"
|
|
393
|
+
};
|
|
394
|
+
const getCategory = (issue) => {
|
|
395
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
396
|
+
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
397
|
+
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics")) return { category: "physics", key: "physics" };
|
|
398
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u5230 ") && issue.includes("Scene \u8F6C\u573A")) {
|
|
399
|
+
const target = issue.match(/未记录到 (.+?) 的 Scene 转场/)?.[1] ?? "unknown";
|
|
400
|
+
return { category: "transition", key: `transition:${target}` };
|
|
401
|
+
}
|
|
402
|
+
if (issue.includes("\u672A\u8BBF\u95EE Scene")) {
|
|
403
|
+
const scene = issue.match(/未访问 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
404
|
+
return { category: "visit", key: `visit:${scene}` };
|
|
405
|
+
}
|
|
406
|
+
if (issue.includes("\u672A\u91CD\u542F Scene")) {
|
|
407
|
+
const scene = issue.match(/未重启 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
408
|
+
return { category: "restart", key: `restart:${scene}` };
|
|
409
|
+
}
|
|
410
|
+
if (issue.includes("\u7F3A\u5C11 checkpoint")) return { category: "checkpoint", key: `checkpoint:${issue.match(/checkpoint "([^"]+)"/)?.[1] ?? issue}` };
|
|
411
|
+
if (issue.includes("\u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1")) return { category: "destroy", key: "destroy" };
|
|
412
|
+
if (issue.includes("\u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry")) {
|
|
413
|
+
const scene = issue.match(/生产 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
414
|
+
return { category: "registry", key: `registry:${scene}` };
|
|
415
|
+
}
|
|
416
|
+
if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
|
|
417
|
+
if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
|
|
418
|
+
return { category: "other", key: `other:${issue}` };
|
|
419
|
+
};
|
|
420
|
+
for (const rawIssue of [...new Set(issues)]) {
|
|
421
|
+
const compact = rawIssue.replaceAll(/\s+/g, " ").trim();
|
|
422
|
+
const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u68C0\u67E5\u9879 "$1"').replaceAll("evidence requirements", "\u6240\u9700\u8FD0\u884C\u8BC1\u636E").replaceAll("metadata", "\u6D4B\u8BD5\u58F0\u660E").replaceAll("host registry", "\u5DF2\u6CE8\u518C Scene \u6E05\u5355");
|
|
423
|
+
const id = compact.match(/contract "([^"]+)"/)?.[1];
|
|
424
|
+
const { category, key } = getCategory(compact);
|
|
425
|
+
const group = groups.get(key);
|
|
426
|
+
if (group) {
|
|
427
|
+
if (id && !group.ids.includes(id)) group.ids.push(id);
|
|
428
|
+
} else {
|
|
429
|
+
groups.set(key, { text: readable, ids: id ? [id] : [], key });
|
|
430
|
+
}
|
|
431
|
+
if (repairText[category] && !repairKeys.has(category)) {
|
|
432
|
+
repairKeys.add(category);
|
|
433
|
+
repairs.push(repairText[category]);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const grouped = [...groups.values()];
|
|
437
|
+
const visible = grouped.slice(0, 8).map((group) => {
|
|
438
|
+
if (group.ids.length <= 1) return group.text;
|
|
439
|
+
return `${group.text}\uFF08\u53D7\u5F71\u54CD\u68C0\u67E5\u9879\uFF1A${group.ids.join("\u3001")}\uFF09`;
|
|
440
|
+
});
|
|
441
|
+
return {
|
|
442
|
+
issues: visible.map(
|
|
443
|
+
(issue) => issue.length > 360 ? `${issue.slice(0, 357)}...` : issue
|
|
444
|
+
),
|
|
445
|
+
repairs: repairs.slice(0, 8),
|
|
446
|
+
omitted: Math.max(0, grouped.length - visible.length)
|
|
447
|
+
};
|
|
448
|
+
}
|
|
370
449
|
function printIssues(stage, issues) {
|
|
371
450
|
if (issues.length === 0) return;
|
|
372
451
|
console.error(`
|
|
373
452
|
GAMEPLAY_AUDIT: ${stage} FAILED`);
|
|
374
|
-
|
|
453
|
+
console.error(
|
|
454
|
+
"\u73A9\u6CD5\u68C0\u67E5\u5931\u8D25\uFF1A\u4EE5\u4E0B\u6BCF\u4E2A\u68C0\u67E5\u9879\u5BF9\u5E94\u4E00\u4E2A\u9700\u8981\u6D4B\u8BD5\u8BC1\u660E\u7684\u6E38\u620F\u884C\u4E3A\u3002"
|
|
455
|
+
);
|
|
456
|
+
const formatted = formatGameplayIssues(issues);
|
|
457
|
+
for (const issue of formatted.issues) console.error(`- ${issue}`);
|
|
458
|
+
if (formatted.repairs.length > 0) {
|
|
459
|
+
console.error("\u4FEE\u590D\u5EFA\u8BAE\uFF08\u540C\u7C7B\u95EE\u9898\u53EA\u5217\u4E00\u6B21\uFF09\uFF1A");
|
|
460
|
+
for (const repair of formatted.repairs) console.error(`- ${repair}`);
|
|
461
|
+
}
|
|
462
|
+
if (formatted.omitted > 0) {
|
|
463
|
+
console.error(
|
|
464
|
+
`- \u5176\u4F59 ${formatted.omitted} \u7C7B\u95EE\u9898\u5DF2\u7701\u7565\uFF1B\u5148\u4FEE\u590D\u4EE5\u4E0A\u95EE\u9898\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
375
467
|
}
|
|
376
468
|
var GameplayAuditReporter = class {
|
|
377
469
|
projectRoot;
|
|
@@ -407,26 +499,45 @@ var GameplayAuditReporter = class {
|
|
|
407
499
|
for (const issue of result.issues) this.reportPreflightIssue(issue);
|
|
408
500
|
}
|
|
409
501
|
/** 在测试运行结束后汇总全部 contract、Ledger 和 Scene registry,并决定命令退出码。 */
|
|
410
|
-
onTestRunEnd(testModules) {
|
|
502
|
+
onTestRunEnd(testModules, _unhandledErrors = [], reason = "passed") {
|
|
503
|
+
const collectionFailed = reason === "failed" && testModules.some(
|
|
504
|
+
(testModule) => testModule.state() === "failed" && [...testModule.children.allTests()].length === 0
|
|
505
|
+
);
|
|
506
|
+
if (collectionFailed) {
|
|
507
|
+
console.error(
|
|
508
|
+
"\nGAMEPLAY_AUDIT: NOT RUN\n\u73A9\u6CD5\u6D4B\u8BD5\u6587\u4EF6\u65E0\u6CD5\u52A0\u8F7D\uFF0C\u56E0\u6B64\u65E0\u6CD5\u68C0\u67E5\u6E38\u620F\u884C\u4E3A\u3002\u8BF7\u5148\u4FEE\u590D Vitest \u4E0A\u65B9\u663E\u793A\u7684\u8BED\u6CD5\u6216\u5BFC\u5165\u9519\u8BEF\uFF0C\u518D\u8FD0\u884C pnpm test\u3002"
|
|
509
|
+
);
|
|
510
|
+
process.exitCode = 1;
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
411
513
|
const tests = testModules.flatMap(
|
|
412
514
|
(testModule) => [...testModule.children.allTests()].map(toAuditTest)
|
|
413
515
|
);
|
|
414
516
|
const result = auditGameplayRun(this.options, tests);
|
|
415
|
-
|
|
416
|
-
|
|
517
|
+
const combinedIssues = [
|
|
518
|
+
...this.preflightIssues,
|
|
519
|
+
...result.issues
|
|
520
|
+
];
|
|
521
|
+
if (combinedIssues.length > 0) {
|
|
522
|
+
if (this.preflightIssues.size > 0) {
|
|
523
|
+
console.error("\nGAMEPLAY_AUDIT: PREFLIGHT FAILED (included in final summary)");
|
|
524
|
+
}
|
|
525
|
+
printIssues("FINAL", [...new Set(combinedIssues)]);
|
|
417
526
|
process.exitCode = 1;
|
|
418
527
|
} else {
|
|
419
528
|
console.log(
|
|
420
529
|
`
|
|
421
530
|
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${this.options.contracts.length} contracts, ${this.options.scenes.length} scenes)`
|
|
422
531
|
);
|
|
532
|
+
console.log(
|
|
533
|
+
"GAMEPLAY_AUDIT_NOTE: \u73A9\u6CD5\u68C0\u67E5\u5DF2\u901A\u8FC7\uFF1B\u4ECD\u9700\u4EE5\u6700\u540E\u7684 TEST_RESULT \u5224\u65AD\u6574\u4F53\u6210\u529F\u3002"
|
|
534
|
+
);
|
|
423
535
|
}
|
|
424
536
|
}
|
|
425
537
|
/** 立即输出一个此前未报告过的预检缺口,并让本次测试命令失败。 */
|
|
426
538
|
reportPreflightIssue(issue) {
|
|
427
539
|
if (this.preflightIssues.has(issue)) return;
|
|
428
540
|
this.preflightIssues.add(issue);
|
|
429
|
-
printIssues("PREFLIGHT", [issue]);
|
|
430
541
|
process.exitCode = 1;
|
|
431
542
|
}
|
|
432
543
|
};
|
|
@@ -476,7 +587,9 @@ function defineGameVitestConfig(options) {
|
|
|
476
587
|
...options.additionalSetupFiles ?? []
|
|
477
588
|
],
|
|
478
589
|
reporters: [
|
|
479
|
-
|
|
590
|
+
// Vitest's minimal reporter is optimized for AI output: passing tests
|
|
591
|
+
// stay quiet while failed tests retain their useful error message.
|
|
592
|
+
"minimal",
|
|
480
593
|
new GameplayAuditReporter(options.projectRoot, options.gameplayAudit)
|
|
481
594
|
],
|
|
482
595
|
restoreMocks: true,
|
|
@@ -488,7 +601,9 @@ function defineGameVitestConfig(options) {
|
|
|
488
601
|
"src/scenes/**/*.d.ts",
|
|
489
602
|
"src/scenes/**/*.{spec,test}.{ts,tsx}"
|
|
490
603
|
],
|
|
491
|
-
|
|
604
|
+
// Per-file threshold errors still identify omitted Scene files; the
|
|
605
|
+
// summary avoids printing a large coverage table on every successful run.
|
|
606
|
+
reporter: ["text-summary"],
|
|
492
607
|
thresholds: {
|
|
493
608
|
perFile: true,
|
|
494
609
|
lines: 1,
|
|
@@ -535,7 +650,7 @@ describe("defineGameVitestConfig", () => {
|
|
|
535
650
|
environment: "jsdom",
|
|
536
651
|
environmentOptions: { jsdom: { url: "http://localhost/" } },
|
|
537
652
|
setupFiles: ["miaoda-game-devkit/vitest-setup"],
|
|
538
|
-
reporters: ["
|
|
653
|
+
reporters: ["minimal", expect.any(Object)],
|
|
539
654
|
restoreMocks: true,
|
|
540
655
|
clearMocks: true,
|
|
541
656
|
coverage: {
|
|
@@ -545,7 +660,7 @@ describe("defineGameVitestConfig", () => {
|
|
|
545
660
|
"src/scenes/**/*.d.ts",
|
|
546
661
|
"src/scenes/**/*.{spec,test}.{ts,tsx}"
|
|
547
662
|
],
|
|
548
|
-
reporter: ["text"],
|
|
663
|
+
reporter: ["text-summary"],
|
|
549
664
|
thresholds: {
|
|
550
665
|
perFile: true,
|
|
551
666
|
lines: 1,
|
package/dist/vitest-config.js
CHANGED
|
@@ -242,7 +242,7 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
242
242
|
const matches = actual.filter((test) => test.metadata?.id === contract.id);
|
|
243
243
|
if (matches.length === 0) {
|
|
244
244
|
issues.push(
|
|
245
|
-
`\
|
|
245
|
+
`\u6CA1\u6709\u627E\u5230\u68C0\u67E5\u9879 "${contract.id}" \u7684\u53EF\u6267\u884C\u6D4B\u8BD5\uFF08${normalizedFile}\uFF09\u3002`
|
|
246
246
|
);
|
|
247
247
|
} else if (matches.length > 1) {
|
|
248
248
|
issues.push(
|
|
@@ -273,12 +273,15 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
273
273
|
function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
274
274
|
const issues = [];
|
|
275
275
|
const inputEvents = evidence.mouseEvents + evidence.keyboardEvents + evidence.touchEvents;
|
|
276
|
-
if (requirements.requireInput && inputEvents === 0)
|
|
276
|
+
if (requirements.requireInput && inputEvents === 0) {
|
|
277
277
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165\u3002`);
|
|
278
|
-
|
|
278
|
+
}
|
|
279
|
+
if (requirements.requireFrameAdvance && evidence.frames === 0) {
|
|
279
280
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27\u3002`);
|
|
280
|
-
|
|
281
|
+
}
|
|
282
|
+
if (requirements.requirePhysicsStep && evidence.physicsSteps === 0) {
|
|
281
283
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB Arcade Physics\u3002`);
|
|
284
|
+
}
|
|
282
285
|
for (const target of normalizeList(requirements.requireTransition)) {
|
|
283
286
|
if (!evidence.transitions.some((transition) => transition.to === target)) {
|
|
284
287
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u5230 ${target} \u7684 Scene \u8F6C\u573A\u3002`);
|
|
@@ -289,21 +292,24 @@ function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
|
289
292
|
issues.push(`contract "${id}" \u672A\u8BBF\u95EE Scene "${scene}"\u3002`);
|
|
290
293
|
}
|
|
291
294
|
for (const scene of normalizeList(requirements.requireRestart)) {
|
|
292
|
-
if (!evidence.restartedScenes.includes(scene))
|
|
295
|
+
if (!evidence.restartedScenes.includes(scene)) {
|
|
293
296
|
issues.push(`contract "${id}" \u672A\u91CD\u542F Scene "${scene}"\u3002`);
|
|
297
|
+
}
|
|
294
298
|
}
|
|
295
299
|
for (const checkpoint of normalizeList(requirements.requireCheckpoint)) {
|
|
296
|
-
if (!evidence.checkpoints.includes(checkpoint))
|
|
300
|
+
if (!evidence.checkpoints.includes(checkpoint)) {
|
|
297
301
|
issues.push(`contract "${id}" \u7F3A\u5C11 checkpoint "${checkpoint}"\u3002`);
|
|
302
|
+
}
|
|
298
303
|
}
|
|
299
|
-
if (requirements.requireDestroy && !evidence.destroyed)
|
|
304
|
+
if (requirements.requireDestroy && !evidence.destroyed) {
|
|
300
305
|
issues.push(`contract "${id}" \u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1\u3002`);
|
|
306
|
+
}
|
|
301
307
|
return issues;
|
|
302
308
|
}
|
|
303
309
|
function auditGameplayRun(options, tests) {
|
|
304
310
|
const issues = [];
|
|
305
311
|
const actualContracts = tests.filter((test) => test.metadata);
|
|
306
|
-
const
|
|
312
|
+
const observedEvidence = [];
|
|
307
313
|
for (const expected of options.contracts) {
|
|
308
314
|
const matches = actualContracts.filter(
|
|
309
315
|
(test2) => test2.metadata?.id === expected.id
|
|
@@ -319,6 +325,7 @@ function auditGameplayRun(options, tests) {
|
|
|
319
325
|
const test = matches[0];
|
|
320
326
|
const metadata = test?.metadata;
|
|
321
327
|
if (!test || !metadata) continue;
|
|
328
|
+
if (metadata.evidence) observedEvidence.push(metadata.evidence);
|
|
322
329
|
if (test.file.replaceAll("\\", "/") !== expected.testFile.replaceAll("\\", "/")) {
|
|
323
330
|
issues.push(
|
|
324
331
|
`contract "${expected.id}" \u5FC5\u987B\u4F4D\u4E8E ${expected.testFile}\uFF0C\u5B9E\u9645\u4F4D\u4E8E ${test.file}\u3002`
|
|
@@ -339,7 +346,6 @@ function auditGameplayRun(options, tests) {
|
|
|
339
346
|
metadata.evidence
|
|
340
347
|
);
|
|
341
348
|
issues.push(...evidenceIssues);
|
|
342
|
-
if (evidenceIssues.length === 0) acceptedEvidence.push(metadata.evidence);
|
|
343
349
|
}
|
|
344
350
|
for (const test of actualContracts) {
|
|
345
351
|
const id = test.metadata?.id;
|
|
@@ -350,14 +356,15 @@ function auditGameplayRun(options, tests) {
|
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
358
|
const registeredScenes = normalizeSet(
|
|
353
|
-
|
|
359
|
+
observedEvidence.flatMap((evidence) => evidence.registeredScenes)
|
|
354
360
|
);
|
|
355
361
|
const expectedScenes = normalizeSet(options.scenes);
|
|
356
362
|
for (const scene of expectedScenes) {
|
|
357
|
-
if (!registeredScenes.includes(scene))
|
|
363
|
+
if (!registeredScenes.includes(scene)) {
|
|
358
364
|
issues.push(
|
|
359
|
-
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\
|
|
365
|
+
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry \u4E2D\u3002`
|
|
360
366
|
);
|
|
367
|
+
}
|
|
361
368
|
}
|
|
362
369
|
for (const scene of registeredScenes) {
|
|
363
370
|
if (!expectedScenes.includes(scene))
|
|
@@ -387,11 +394,96 @@ function toAuditTest(test) {
|
|
|
387
394
|
metadata: isGameplayContractMetadata(metadata) ? metadata : void 0
|
|
388
395
|
};
|
|
389
396
|
}
|
|
397
|
+
function formatGameplayIssues(issues) {
|
|
398
|
+
const groups = /* @__PURE__ */ new Map();
|
|
399
|
+
const repairKeys = /* @__PURE__ */ new Set();
|
|
400
|
+
const repairs = [];
|
|
401
|
+
const repairText = {
|
|
402
|
+
input: "\u8F93\u5165\uFF1A\u5148\u63A8\u8FDB\u4E00\u5E27\uFF0C\u518D\u901A\u8FC7 host.input.mouse\u3001host.input.keyboard \u6216 host.input.touch \u9A71\u52A8\u771F\u5B9E\u751F\u4EA7\u8F93\u5165\u3002",
|
|
403
|
+
frames: "\u5E27\u63A8\u8FDB\uFF1A\u5728\u8F93\u5165\u6216\u751F\u4EA7\u547D\u4EE4\u540E\u8C03\u7528 host.stepFrames()\uFF0C\u5F02\u6B65 update \u4F7F\u7528 host.stepFramesAsync()\u3002",
|
|
404
|
+
physics: "\u7269\u7406\uFF1A\u4EC5\u5728\u751F\u4EA7\u4F7F\u7528 Arcade Physics \u65F6\u8C03\u7528 host.stepPhysics()\uFF1B\u5176\u4ED6\u7269\u7406\u7CFB\u7EDF\u4F7F\u7528\u5B8C\u6574\u5E27\u3002",
|
|
405
|
+
transition: "\u8F6C\u573A\uFF1Aboot \u524D\u6CE8\u518C\u76EE\u6807 Scene\uFF0C\u8C03\u7528\u751F\u4EA7\u8F6C\u573A\u547D\u4EE4\uFF0C\u518D\u7528 host.stepUntil() \u7B49\u5F85\u76EE\u6807\u6FC0\u6D3B\u5E76\u65AD\u8A00\u6E90/\u76EE\u6807\u72B6\u6001\u3002",
|
|
406
|
+
visit: "Scene\uFF1A\u786E\u8BA4\u6D4B\u8BD5\u901A\u8FC7\u771F\u5B9E\u5165\u53E3\u6216\u5408\u6CD5\u8F6C\u573A\u8BBF\u95EE\u8BE5 Scene\uFF0C\u5E76\u4F7F\u7528\u771F\u5B9E\u914D\u7F6E\u542F\u52A8\u3002",
|
|
407
|
+
restart: "\u6062\u590D\uFF1A\u901A\u8FC7\u771F\u5B9E\u8F93\u5165\u6216\u751F\u4EA7 controls \u89E6\u53D1 restart\uFF0C\u7B49\u5F85\u5B8C\u6574\u5E27\u5E76\u65AD\u8A00\u72B6\u6001\u5DF2\u6062\u590D\u3002",
|
|
408
|
+
checkpoint: "\u68C0\u67E5\u70B9\uFF1A\u5148\u65AD\u8A00 authoritative state\uFF0C\u518D\u8C03\u7528 host.checkpoint()\uFF1B\u4E0D\u8981\u8C03\u7528 contract.checkpoint()\u3002",
|
|
409
|
+
destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
|
|
410
|
+
registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4 Scene \u5728\u751F\u4EA7 registry \u4E2D\uFF0C\u5E76\u5728 boot \u524D\u901A\u8FC7 additionalScenes \u6CE8\u518C\u8F6C\u573A\u76EE\u6807\u3002",
|
|
411
|
+
missing: "\u6D4B\u8BD5\u7F3A\u5931\uFF1A\u5728\u6307\u5B9A\u6587\u4EF6\u4F7F\u7528 gameplayTest \u5B9A\u4E49\u68C0\u67E5\u9879\uFF0C\u5E76\u901A\u8FC7 contract.attachHost(host) \u7ED1\u5B9A host\u3002",
|
|
412
|
+
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF1B\u68C0\u67E5\u9879\u53EA\u6C47\u603B\u7ED3\u679C\uFF0C\u4E0D\u662F\u8FD0\u884C\u65F6\u5BF9\u8C61\u3002"
|
|
413
|
+
};
|
|
414
|
+
const getCategory = (issue) => {
|
|
415
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
416
|
+
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
417
|
+
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics")) return { category: "physics", key: "physics" };
|
|
418
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u5230 ") && issue.includes("Scene \u8F6C\u573A")) {
|
|
419
|
+
const target = issue.match(/未记录到 (.+?) 的 Scene 转场/)?.[1] ?? "unknown";
|
|
420
|
+
return { category: "transition", key: `transition:${target}` };
|
|
421
|
+
}
|
|
422
|
+
if (issue.includes("\u672A\u8BBF\u95EE Scene")) {
|
|
423
|
+
const scene = issue.match(/未访问 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
424
|
+
return { category: "visit", key: `visit:${scene}` };
|
|
425
|
+
}
|
|
426
|
+
if (issue.includes("\u672A\u91CD\u542F Scene")) {
|
|
427
|
+
const scene = issue.match(/未重启 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
428
|
+
return { category: "restart", key: `restart:${scene}` };
|
|
429
|
+
}
|
|
430
|
+
if (issue.includes("\u7F3A\u5C11 checkpoint")) return { category: "checkpoint", key: `checkpoint:${issue.match(/checkpoint "([^"]+)"/)?.[1] ?? issue}` };
|
|
431
|
+
if (issue.includes("\u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1")) return { category: "destroy", key: "destroy" };
|
|
432
|
+
if (issue.includes("\u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry")) {
|
|
433
|
+
const scene = issue.match(/生产 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
434
|
+
return { category: "registry", key: `registry:${scene}` };
|
|
435
|
+
}
|
|
436
|
+
if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
|
|
437
|
+
if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
|
|
438
|
+
return { category: "other", key: `other:${issue}` };
|
|
439
|
+
};
|
|
440
|
+
for (const rawIssue of [...new Set(issues)]) {
|
|
441
|
+
const compact = rawIssue.replaceAll(/\s+/g, " ").trim();
|
|
442
|
+
const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u68C0\u67E5\u9879 "$1"').replaceAll("evidence requirements", "\u6240\u9700\u8FD0\u884C\u8BC1\u636E").replaceAll("metadata", "\u6D4B\u8BD5\u58F0\u660E").replaceAll("host registry", "\u5DF2\u6CE8\u518C Scene \u6E05\u5355");
|
|
443
|
+
const id = compact.match(/contract "([^"]+)"/)?.[1];
|
|
444
|
+
const { category, key } = getCategory(compact);
|
|
445
|
+
const group = groups.get(key);
|
|
446
|
+
if (group) {
|
|
447
|
+
if (id && !group.ids.includes(id)) group.ids.push(id);
|
|
448
|
+
} else {
|
|
449
|
+
groups.set(key, { text: readable, ids: id ? [id] : [], key });
|
|
450
|
+
}
|
|
451
|
+
if (repairText[category] && !repairKeys.has(category)) {
|
|
452
|
+
repairKeys.add(category);
|
|
453
|
+
repairs.push(repairText[category]);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const grouped = [...groups.values()];
|
|
457
|
+
const visible = grouped.slice(0, 8).map((group) => {
|
|
458
|
+
if (group.ids.length <= 1) return group.text;
|
|
459
|
+
return `${group.text}\uFF08\u53D7\u5F71\u54CD\u68C0\u67E5\u9879\uFF1A${group.ids.join("\u3001")}\uFF09`;
|
|
460
|
+
});
|
|
461
|
+
return {
|
|
462
|
+
issues: visible.map(
|
|
463
|
+
(issue) => issue.length > 360 ? `${issue.slice(0, 357)}...` : issue
|
|
464
|
+
),
|
|
465
|
+
repairs: repairs.slice(0, 8),
|
|
466
|
+
omitted: Math.max(0, grouped.length - visible.length)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
390
469
|
function printIssues(stage, issues) {
|
|
391
470
|
if (issues.length === 0) return;
|
|
392
471
|
console.error(`
|
|
393
472
|
GAMEPLAY_AUDIT: ${stage} FAILED`);
|
|
394
|
-
|
|
473
|
+
console.error(
|
|
474
|
+
"\u73A9\u6CD5\u68C0\u67E5\u5931\u8D25\uFF1A\u4EE5\u4E0B\u6BCF\u4E2A\u68C0\u67E5\u9879\u5BF9\u5E94\u4E00\u4E2A\u9700\u8981\u6D4B\u8BD5\u8BC1\u660E\u7684\u6E38\u620F\u884C\u4E3A\u3002"
|
|
475
|
+
);
|
|
476
|
+
const formatted = formatGameplayIssues(issues);
|
|
477
|
+
for (const issue of formatted.issues) console.error(`- ${issue}`);
|
|
478
|
+
if (formatted.repairs.length > 0) {
|
|
479
|
+
console.error("\u4FEE\u590D\u5EFA\u8BAE\uFF08\u540C\u7C7B\u95EE\u9898\u53EA\u5217\u4E00\u6B21\uFF09\uFF1A");
|
|
480
|
+
for (const repair of formatted.repairs) console.error(`- ${repair}`);
|
|
481
|
+
}
|
|
482
|
+
if (formatted.omitted > 0) {
|
|
483
|
+
console.error(
|
|
484
|
+
`- \u5176\u4F59 ${formatted.omitted} \u7C7B\u95EE\u9898\u5DF2\u7701\u7565\uFF1B\u5148\u4FEE\u590D\u4EE5\u4E0A\u95EE\u9898\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
395
487
|
}
|
|
396
488
|
var GameplayAuditReporter = class {
|
|
397
489
|
projectRoot;
|
|
@@ -427,26 +519,45 @@ var GameplayAuditReporter = class {
|
|
|
427
519
|
for (const issue of result.issues) this.reportPreflightIssue(issue);
|
|
428
520
|
}
|
|
429
521
|
/** 在测试运行结束后汇总全部 contract、Ledger 和 Scene registry,并决定命令退出码。 */
|
|
430
|
-
onTestRunEnd(testModules) {
|
|
522
|
+
onTestRunEnd(testModules, _unhandledErrors = [], reason = "passed") {
|
|
523
|
+
const collectionFailed = reason === "failed" && testModules.some(
|
|
524
|
+
(testModule) => testModule.state() === "failed" && [...testModule.children.allTests()].length === 0
|
|
525
|
+
);
|
|
526
|
+
if (collectionFailed) {
|
|
527
|
+
console.error(
|
|
528
|
+
"\nGAMEPLAY_AUDIT: NOT RUN\n\u73A9\u6CD5\u6D4B\u8BD5\u6587\u4EF6\u65E0\u6CD5\u52A0\u8F7D\uFF0C\u56E0\u6B64\u65E0\u6CD5\u68C0\u67E5\u6E38\u620F\u884C\u4E3A\u3002\u8BF7\u5148\u4FEE\u590D Vitest \u4E0A\u65B9\u663E\u793A\u7684\u8BED\u6CD5\u6216\u5BFC\u5165\u9519\u8BEF\uFF0C\u518D\u8FD0\u884C pnpm test\u3002"
|
|
529
|
+
);
|
|
530
|
+
process.exitCode = 1;
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
431
533
|
const tests = testModules.flatMap(
|
|
432
534
|
(testModule) => [...testModule.children.allTests()].map(toAuditTest)
|
|
433
535
|
);
|
|
434
536
|
const result = auditGameplayRun(this.options, tests);
|
|
435
|
-
|
|
436
|
-
|
|
537
|
+
const combinedIssues = [
|
|
538
|
+
...this.preflightIssues,
|
|
539
|
+
...result.issues
|
|
540
|
+
];
|
|
541
|
+
if (combinedIssues.length > 0) {
|
|
542
|
+
if (this.preflightIssues.size > 0) {
|
|
543
|
+
console.error("\nGAMEPLAY_AUDIT: PREFLIGHT FAILED (included in final summary)");
|
|
544
|
+
}
|
|
545
|
+
printIssues("FINAL", [...new Set(combinedIssues)]);
|
|
437
546
|
process.exitCode = 1;
|
|
438
547
|
} else {
|
|
439
548
|
console.log(
|
|
440
549
|
`
|
|
441
550
|
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${this.options.contracts.length} contracts, ${this.options.scenes.length} scenes)`
|
|
442
551
|
);
|
|
552
|
+
console.log(
|
|
553
|
+
"GAMEPLAY_AUDIT_NOTE: \u73A9\u6CD5\u68C0\u67E5\u5DF2\u901A\u8FC7\uFF1B\u4ECD\u9700\u4EE5\u6700\u540E\u7684 TEST_RESULT \u5224\u65AD\u6574\u4F53\u6210\u529F\u3002"
|
|
554
|
+
);
|
|
443
555
|
}
|
|
444
556
|
}
|
|
445
557
|
/** 立即输出一个此前未报告过的预检缺口,并让本次测试命令失败。 */
|
|
446
558
|
reportPreflightIssue(issue) {
|
|
447
559
|
if (this.preflightIssues.has(issue)) return;
|
|
448
560
|
this.preflightIssues.add(issue);
|
|
449
|
-
printIssues("PREFLIGHT", [issue]);
|
|
450
561
|
process.exitCode = 1;
|
|
451
562
|
}
|
|
452
563
|
};
|
|
@@ -496,7 +607,9 @@ function defineGameVitestConfig(options) {
|
|
|
496
607
|
...options.additionalSetupFiles ?? []
|
|
497
608
|
],
|
|
498
609
|
reporters: [
|
|
499
|
-
|
|
610
|
+
// Vitest's minimal reporter is optimized for AI output: passing tests
|
|
611
|
+
// stay quiet while failed tests retain their useful error message.
|
|
612
|
+
"minimal",
|
|
500
613
|
new GameplayAuditReporter(options.projectRoot, options.gameplayAudit)
|
|
501
614
|
],
|
|
502
615
|
restoreMocks: true,
|
|
@@ -508,7 +621,9 @@ function defineGameVitestConfig(options) {
|
|
|
508
621
|
"src/scenes/**/*.d.ts",
|
|
509
622
|
"src/scenes/**/*.{spec,test}.{ts,tsx}"
|
|
510
623
|
],
|
|
511
|
-
|
|
624
|
+
// Per-file threshold errors still identify omitted Scene files; the
|
|
625
|
+
// summary avoids printing a large coverage table on every successful run.
|
|
626
|
+
reporter: ["text-summary"],
|
|
512
627
|
thresholds: {
|
|
513
628
|
perFile: true,
|
|
514
629
|
lines: 1,
|
package/dist/vitest-config.mjs
CHANGED
|
@@ -218,7 +218,7 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
218
218
|
const matches = actual.filter((test) => test.metadata?.id === contract.id);
|
|
219
219
|
if (matches.length === 0) {
|
|
220
220
|
issues.push(
|
|
221
|
-
`\
|
|
221
|
+
`\u6CA1\u6709\u627E\u5230\u68C0\u67E5\u9879 "${contract.id}" \u7684\u53EF\u6267\u884C\u6D4B\u8BD5\uFF08${normalizedFile}\uFF09\u3002`
|
|
222
222
|
);
|
|
223
223
|
} else if (matches.length > 1) {
|
|
224
224
|
issues.push(
|
|
@@ -249,12 +249,15 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
249
249
|
function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
250
250
|
const issues = [];
|
|
251
251
|
const inputEvents = evidence.mouseEvents + evidence.keyboardEvents + evidence.touchEvents;
|
|
252
|
-
if (requirements.requireInput && inputEvents === 0)
|
|
252
|
+
if (requirements.requireInput && inputEvents === 0) {
|
|
253
253
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165\u3002`);
|
|
254
|
-
|
|
254
|
+
}
|
|
255
|
+
if (requirements.requireFrameAdvance && evidence.frames === 0) {
|
|
255
256
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27\u3002`);
|
|
256
|
-
|
|
257
|
+
}
|
|
258
|
+
if (requirements.requirePhysicsStep && evidence.physicsSteps === 0) {
|
|
257
259
|
issues.push(`contract "${id}" \u672A\u63A8\u8FDB Arcade Physics\u3002`);
|
|
260
|
+
}
|
|
258
261
|
for (const target of normalizeList(requirements.requireTransition)) {
|
|
259
262
|
if (!evidence.transitions.some((transition) => transition.to === target)) {
|
|
260
263
|
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u5230 ${target} \u7684 Scene \u8F6C\u573A\u3002`);
|
|
@@ -265,21 +268,24 @@ function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
|
265
268
|
issues.push(`contract "${id}" \u672A\u8BBF\u95EE Scene "${scene}"\u3002`);
|
|
266
269
|
}
|
|
267
270
|
for (const scene of normalizeList(requirements.requireRestart)) {
|
|
268
|
-
if (!evidence.restartedScenes.includes(scene))
|
|
271
|
+
if (!evidence.restartedScenes.includes(scene)) {
|
|
269
272
|
issues.push(`contract "${id}" \u672A\u91CD\u542F Scene "${scene}"\u3002`);
|
|
273
|
+
}
|
|
270
274
|
}
|
|
271
275
|
for (const checkpoint of normalizeList(requirements.requireCheckpoint)) {
|
|
272
|
-
if (!evidence.checkpoints.includes(checkpoint))
|
|
276
|
+
if (!evidence.checkpoints.includes(checkpoint)) {
|
|
273
277
|
issues.push(`contract "${id}" \u7F3A\u5C11 checkpoint "${checkpoint}"\u3002`);
|
|
278
|
+
}
|
|
274
279
|
}
|
|
275
|
-
if (requirements.requireDestroy && !evidence.destroyed)
|
|
280
|
+
if (requirements.requireDestroy && !evidence.destroyed) {
|
|
276
281
|
issues.push(`contract "${id}" \u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1\u3002`);
|
|
282
|
+
}
|
|
277
283
|
return issues;
|
|
278
284
|
}
|
|
279
285
|
function auditGameplayRun(options, tests) {
|
|
280
286
|
const issues = [];
|
|
281
287
|
const actualContracts = tests.filter((test) => test.metadata);
|
|
282
|
-
const
|
|
288
|
+
const observedEvidence = [];
|
|
283
289
|
for (const expected of options.contracts) {
|
|
284
290
|
const matches = actualContracts.filter(
|
|
285
291
|
(test2) => test2.metadata?.id === expected.id
|
|
@@ -295,6 +301,7 @@ function auditGameplayRun(options, tests) {
|
|
|
295
301
|
const test = matches[0];
|
|
296
302
|
const metadata = test?.metadata;
|
|
297
303
|
if (!test || !metadata) continue;
|
|
304
|
+
if (metadata.evidence) observedEvidence.push(metadata.evidence);
|
|
298
305
|
if (test.file.replaceAll("\\", "/") !== expected.testFile.replaceAll("\\", "/")) {
|
|
299
306
|
issues.push(
|
|
300
307
|
`contract "${expected.id}" \u5FC5\u987B\u4F4D\u4E8E ${expected.testFile}\uFF0C\u5B9E\u9645\u4F4D\u4E8E ${test.file}\u3002`
|
|
@@ -315,7 +322,6 @@ function auditGameplayRun(options, tests) {
|
|
|
315
322
|
metadata.evidence
|
|
316
323
|
);
|
|
317
324
|
issues.push(...evidenceIssues);
|
|
318
|
-
if (evidenceIssues.length === 0) acceptedEvidence.push(metadata.evidence);
|
|
319
325
|
}
|
|
320
326
|
for (const test of actualContracts) {
|
|
321
327
|
const id = test.metadata?.id;
|
|
@@ -326,14 +332,15 @@ function auditGameplayRun(options, tests) {
|
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
const registeredScenes = normalizeSet(
|
|
329
|
-
|
|
335
|
+
observedEvidence.flatMap((evidence) => evidence.registeredScenes)
|
|
330
336
|
);
|
|
331
337
|
const expectedScenes = normalizeSet(options.scenes);
|
|
332
338
|
for (const scene of expectedScenes) {
|
|
333
|
-
if (!registeredScenes.includes(scene))
|
|
339
|
+
if (!registeredScenes.includes(scene)) {
|
|
334
340
|
issues.push(
|
|
335
|
-
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\
|
|
341
|
+
`\u751F\u4EA7 Scene "${scene}" \u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry \u4E2D\u3002`
|
|
336
342
|
);
|
|
343
|
+
}
|
|
337
344
|
}
|
|
338
345
|
for (const scene of registeredScenes) {
|
|
339
346
|
if (!expectedScenes.includes(scene))
|
|
@@ -363,11 +370,96 @@ function toAuditTest(test) {
|
|
|
363
370
|
metadata: isGameplayContractMetadata(metadata) ? metadata : void 0
|
|
364
371
|
};
|
|
365
372
|
}
|
|
373
|
+
function formatGameplayIssues(issues) {
|
|
374
|
+
const groups = /* @__PURE__ */ new Map();
|
|
375
|
+
const repairKeys = /* @__PURE__ */ new Set();
|
|
376
|
+
const repairs = [];
|
|
377
|
+
const repairText = {
|
|
378
|
+
input: "\u8F93\u5165\uFF1A\u5148\u63A8\u8FDB\u4E00\u5E27\uFF0C\u518D\u901A\u8FC7 host.input.mouse\u3001host.input.keyboard \u6216 host.input.touch \u9A71\u52A8\u771F\u5B9E\u751F\u4EA7\u8F93\u5165\u3002",
|
|
379
|
+
frames: "\u5E27\u63A8\u8FDB\uFF1A\u5728\u8F93\u5165\u6216\u751F\u4EA7\u547D\u4EE4\u540E\u8C03\u7528 host.stepFrames()\uFF0C\u5F02\u6B65 update \u4F7F\u7528 host.stepFramesAsync()\u3002",
|
|
380
|
+
physics: "\u7269\u7406\uFF1A\u4EC5\u5728\u751F\u4EA7\u4F7F\u7528 Arcade Physics \u65F6\u8C03\u7528 host.stepPhysics()\uFF1B\u5176\u4ED6\u7269\u7406\u7CFB\u7EDF\u4F7F\u7528\u5B8C\u6574\u5E27\u3002",
|
|
381
|
+
transition: "\u8F6C\u573A\uFF1Aboot \u524D\u6CE8\u518C\u76EE\u6807 Scene\uFF0C\u8C03\u7528\u751F\u4EA7\u8F6C\u573A\u547D\u4EE4\uFF0C\u518D\u7528 host.stepUntil() \u7B49\u5F85\u76EE\u6807\u6FC0\u6D3B\u5E76\u65AD\u8A00\u6E90/\u76EE\u6807\u72B6\u6001\u3002",
|
|
382
|
+
visit: "Scene\uFF1A\u786E\u8BA4\u6D4B\u8BD5\u901A\u8FC7\u771F\u5B9E\u5165\u53E3\u6216\u5408\u6CD5\u8F6C\u573A\u8BBF\u95EE\u8BE5 Scene\uFF0C\u5E76\u4F7F\u7528\u771F\u5B9E\u914D\u7F6E\u542F\u52A8\u3002",
|
|
383
|
+
restart: "\u6062\u590D\uFF1A\u901A\u8FC7\u771F\u5B9E\u8F93\u5165\u6216\u751F\u4EA7 controls \u89E6\u53D1 restart\uFF0C\u7B49\u5F85\u5B8C\u6574\u5E27\u5E76\u65AD\u8A00\u72B6\u6001\u5DF2\u6062\u590D\u3002",
|
|
384
|
+
checkpoint: "\u68C0\u67E5\u70B9\uFF1A\u5148\u65AD\u8A00 authoritative state\uFF0C\u518D\u8C03\u7528 host.checkpoint()\uFF1B\u4E0D\u8981\u8C03\u7528 contract.checkpoint()\u3002",
|
|
385
|
+
destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
|
|
386
|
+
registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4 Scene \u5728\u751F\u4EA7 registry \u4E2D\uFF0C\u5E76\u5728 boot \u524D\u901A\u8FC7 additionalScenes \u6CE8\u518C\u8F6C\u573A\u76EE\u6807\u3002",
|
|
387
|
+
missing: "\u6D4B\u8BD5\u7F3A\u5931\uFF1A\u5728\u6307\u5B9A\u6587\u4EF6\u4F7F\u7528 gameplayTest \u5B9A\u4E49\u68C0\u67E5\u9879\uFF0C\u5E76\u901A\u8FC7 contract.attachHost(host) \u7ED1\u5B9A host\u3002",
|
|
388
|
+
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF1B\u68C0\u67E5\u9879\u53EA\u6C47\u603B\u7ED3\u679C\uFF0C\u4E0D\u662F\u8FD0\u884C\u65F6\u5BF9\u8C61\u3002"
|
|
389
|
+
};
|
|
390
|
+
const getCategory = (issue) => {
|
|
391
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
392
|
+
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
393
|
+
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics")) return { category: "physics", key: "physics" };
|
|
394
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u5230 ") && issue.includes("Scene \u8F6C\u573A")) {
|
|
395
|
+
const target = issue.match(/未记录到 (.+?) 的 Scene 转场/)?.[1] ?? "unknown";
|
|
396
|
+
return { category: "transition", key: `transition:${target}` };
|
|
397
|
+
}
|
|
398
|
+
if (issue.includes("\u672A\u8BBF\u95EE Scene")) {
|
|
399
|
+
const scene = issue.match(/未访问 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
400
|
+
return { category: "visit", key: `visit:${scene}` };
|
|
401
|
+
}
|
|
402
|
+
if (issue.includes("\u672A\u91CD\u542F Scene")) {
|
|
403
|
+
const scene = issue.match(/未重启 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
404
|
+
return { category: "restart", key: `restart:${scene}` };
|
|
405
|
+
}
|
|
406
|
+
if (issue.includes("\u7F3A\u5C11 checkpoint")) return { category: "checkpoint", key: `checkpoint:${issue.match(/checkpoint "([^"]+)"/)?.[1] ?? issue}` };
|
|
407
|
+
if (issue.includes("\u672A\u5B8C\u6210 HEADLESS host \u9500\u6BC1")) return { category: "destroy", key: "destroy" };
|
|
408
|
+
if (issue.includes("\u672A\u51FA\u73B0\u5728\u6D4B\u8BD5\u5B9E\u9645\u8BB0\u5F55\u7684 host registry")) {
|
|
409
|
+
const scene = issue.match(/生产 Scene "([^"]+)"/)?.[1] ?? "unknown";
|
|
410
|
+
return { category: "registry", key: `registry:${scene}` };
|
|
411
|
+
}
|
|
412
|
+
if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
|
|
413
|
+
if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
|
|
414
|
+
return { category: "other", key: `other:${issue}` };
|
|
415
|
+
};
|
|
416
|
+
for (const rawIssue of [...new Set(issues)]) {
|
|
417
|
+
const compact = rawIssue.replaceAll(/\s+/g, " ").trim();
|
|
418
|
+
const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u68C0\u67E5\u9879 "$1"').replaceAll("evidence requirements", "\u6240\u9700\u8FD0\u884C\u8BC1\u636E").replaceAll("metadata", "\u6D4B\u8BD5\u58F0\u660E").replaceAll("host registry", "\u5DF2\u6CE8\u518C Scene \u6E05\u5355");
|
|
419
|
+
const id = compact.match(/contract "([^"]+)"/)?.[1];
|
|
420
|
+
const { category, key } = getCategory(compact);
|
|
421
|
+
const group = groups.get(key);
|
|
422
|
+
if (group) {
|
|
423
|
+
if (id && !group.ids.includes(id)) group.ids.push(id);
|
|
424
|
+
} else {
|
|
425
|
+
groups.set(key, { text: readable, ids: id ? [id] : [], key });
|
|
426
|
+
}
|
|
427
|
+
if (repairText[category] && !repairKeys.has(category)) {
|
|
428
|
+
repairKeys.add(category);
|
|
429
|
+
repairs.push(repairText[category]);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const grouped = [...groups.values()];
|
|
433
|
+
const visible = grouped.slice(0, 8).map((group) => {
|
|
434
|
+
if (group.ids.length <= 1) return group.text;
|
|
435
|
+
return `${group.text}\uFF08\u53D7\u5F71\u54CD\u68C0\u67E5\u9879\uFF1A${group.ids.join("\u3001")}\uFF09`;
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
issues: visible.map(
|
|
439
|
+
(issue) => issue.length > 360 ? `${issue.slice(0, 357)}...` : issue
|
|
440
|
+
),
|
|
441
|
+
repairs: repairs.slice(0, 8),
|
|
442
|
+
omitted: Math.max(0, grouped.length - visible.length)
|
|
443
|
+
};
|
|
444
|
+
}
|
|
366
445
|
function printIssues(stage, issues) {
|
|
367
446
|
if (issues.length === 0) return;
|
|
368
447
|
console.error(`
|
|
369
448
|
GAMEPLAY_AUDIT: ${stage} FAILED`);
|
|
370
|
-
|
|
449
|
+
console.error(
|
|
450
|
+
"\u73A9\u6CD5\u68C0\u67E5\u5931\u8D25\uFF1A\u4EE5\u4E0B\u6BCF\u4E2A\u68C0\u67E5\u9879\u5BF9\u5E94\u4E00\u4E2A\u9700\u8981\u6D4B\u8BD5\u8BC1\u660E\u7684\u6E38\u620F\u884C\u4E3A\u3002"
|
|
451
|
+
);
|
|
452
|
+
const formatted = formatGameplayIssues(issues);
|
|
453
|
+
for (const issue of formatted.issues) console.error(`- ${issue}`);
|
|
454
|
+
if (formatted.repairs.length > 0) {
|
|
455
|
+
console.error("\u4FEE\u590D\u5EFA\u8BAE\uFF08\u540C\u7C7B\u95EE\u9898\u53EA\u5217\u4E00\u6B21\uFF09\uFF1A");
|
|
456
|
+
for (const repair of formatted.repairs) console.error(`- ${repair}`);
|
|
457
|
+
}
|
|
458
|
+
if (formatted.omitted > 0) {
|
|
459
|
+
console.error(
|
|
460
|
+
`- \u5176\u4F59 ${formatted.omitted} \u7C7B\u95EE\u9898\u5DF2\u7701\u7565\uFF1B\u5148\u4FEE\u590D\u4EE5\u4E0A\u95EE\u9898\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
371
463
|
}
|
|
372
464
|
var GameplayAuditReporter = class {
|
|
373
465
|
projectRoot;
|
|
@@ -403,26 +495,45 @@ var GameplayAuditReporter = class {
|
|
|
403
495
|
for (const issue of result.issues) this.reportPreflightIssue(issue);
|
|
404
496
|
}
|
|
405
497
|
/** 在测试运行结束后汇总全部 contract、Ledger 和 Scene registry,并决定命令退出码。 */
|
|
406
|
-
onTestRunEnd(testModules) {
|
|
498
|
+
onTestRunEnd(testModules, _unhandledErrors = [], reason = "passed") {
|
|
499
|
+
const collectionFailed = reason === "failed" && testModules.some(
|
|
500
|
+
(testModule) => testModule.state() === "failed" && [...testModule.children.allTests()].length === 0
|
|
501
|
+
);
|
|
502
|
+
if (collectionFailed) {
|
|
503
|
+
console.error(
|
|
504
|
+
"\nGAMEPLAY_AUDIT: NOT RUN\n\u73A9\u6CD5\u6D4B\u8BD5\u6587\u4EF6\u65E0\u6CD5\u52A0\u8F7D\uFF0C\u56E0\u6B64\u65E0\u6CD5\u68C0\u67E5\u6E38\u620F\u884C\u4E3A\u3002\u8BF7\u5148\u4FEE\u590D Vitest \u4E0A\u65B9\u663E\u793A\u7684\u8BED\u6CD5\u6216\u5BFC\u5165\u9519\u8BEF\uFF0C\u518D\u8FD0\u884C pnpm test\u3002"
|
|
505
|
+
);
|
|
506
|
+
process.exitCode = 1;
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
407
509
|
const tests = testModules.flatMap(
|
|
408
510
|
(testModule) => [...testModule.children.allTests()].map(toAuditTest)
|
|
409
511
|
);
|
|
410
512
|
const result = auditGameplayRun(this.options, tests);
|
|
411
|
-
|
|
412
|
-
|
|
513
|
+
const combinedIssues = [
|
|
514
|
+
...this.preflightIssues,
|
|
515
|
+
...result.issues
|
|
516
|
+
];
|
|
517
|
+
if (combinedIssues.length > 0) {
|
|
518
|
+
if (this.preflightIssues.size > 0) {
|
|
519
|
+
console.error("\nGAMEPLAY_AUDIT: PREFLIGHT FAILED (included in final summary)");
|
|
520
|
+
}
|
|
521
|
+
printIssues("FINAL", [...new Set(combinedIssues)]);
|
|
413
522
|
process.exitCode = 1;
|
|
414
523
|
} else {
|
|
415
524
|
console.log(
|
|
416
525
|
`
|
|
417
526
|
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${this.options.contracts.length} contracts, ${this.options.scenes.length} scenes)`
|
|
418
527
|
);
|
|
528
|
+
console.log(
|
|
529
|
+
"GAMEPLAY_AUDIT_NOTE: \u73A9\u6CD5\u68C0\u67E5\u5DF2\u901A\u8FC7\uFF1B\u4ECD\u9700\u4EE5\u6700\u540E\u7684 TEST_RESULT \u5224\u65AD\u6574\u4F53\u6210\u529F\u3002"
|
|
530
|
+
);
|
|
419
531
|
}
|
|
420
532
|
}
|
|
421
533
|
/** 立即输出一个此前未报告过的预检缺口,并让本次测试命令失败。 */
|
|
422
534
|
reportPreflightIssue(issue) {
|
|
423
535
|
if (this.preflightIssues.has(issue)) return;
|
|
424
536
|
this.preflightIssues.add(issue);
|
|
425
|
-
printIssues("PREFLIGHT", [issue]);
|
|
426
537
|
process.exitCode = 1;
|
|
427
538
|
}
|
|
428
539
|
};
|
|
@@ -472,7 +583,9 @@ function defineGameVitestConfig(options) {
|
|
|
472
583
|
...options.additionalSetupFiles ?? []
|
|
473
584
|
],
|
|
474
585
|
reporters: [
|
|
475
|
-
|
|
586
|
+
// Vitest's minimal reporter is optimized for AI output: passing tests
|
|
587
|
+
// stay quiet while failed tests retain their useful error message.
|
|
588
|
+
"minimal",
|
|
476
589
|
new GameplayAuditReporter(options.projectRoot, options.gameplayAudit)
|
|
477
590
|
],
|
|
478
591
|
restoreMocks: true,
|
|
@@ -484,7 +597,9 @@ function defineGameVitestConfig(options) {
|
|
|
484
597
|
"src/scenes/**/*.d.ts",
|
|
485
598
|
"src/scenes/**/*.{spec,test}.{ts,tsx}"
|
|
486
599
|
],
|
|
487
|
-
|
|
600
|
+
// Per-file threshold errors still identify omitted Scene files; the
|
|
601
|
+
// summary avoids printing a large coverage table on every successful run.
|
|
602
|
+
reporter: ["text-summary"],
|
|
488
603
|
thresholds: {
|
|
489
604
|
perFile: true,
|
|
490
605
|
lines: 1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miaoda-game-devkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Shared lint and deterministic Phaser HEADLESS testing tools for Miaoda games",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -43,18 +43,6 @@
|
|
|
43
43
|
"!dist/**/*.map",
|
|
44
44
|
"!dist/lint/**/*.test.*"
|
|
45
45
|
],
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsup",
|
|
48
|
-
"prepack": "pnpm run build",
|
|
49
|
-
"release": "pnpm run test && clean-publish",
|
|
50
|
-
"release:dry-run": "pnpm run test && clean-publish --dry-run",
|
|
51
|
-
"release:beta": "pnpm run test && npm version prerelease --preid=beta && clean-publish --tag beta",
|
|
52
|
-
"release:patch": "pnpm run test && npm version patch && clean-publish",
|
|
53
|
-
"release:minor": "pnpm run test && npm version minor && clean-publish",
|
|
54
|
-
"release:major": "pnpm run test && npm version major && clean-publish",
|
|
55
|
-
"typecheck": "tsc --noEmit",
|
|
56
|
-
"test": "pnpm run build && ./bin/miaoda-game-lint.js --contracts-only"
|
|
57
|
-
},
|
|
58
46
|
"nx": {
|
|
59
47
|
"tags": [
|
|
60
48
|
"type:package",
|
|
@@ -94,23 +82,8 @@
|
|
|
94
82
|
"peerDependencies": {
|
|
95
83
|
"phaser": ">=4.0.0 <5"
|
|
96
84
|
},
|
|
97
|
-
"devDependencies": {
|
|
98
|
-
"@types/eslint": "^9.6.1",
|
|
99
|
-
"@types/estree": "^1.0.9",
|
|
100
|
-
"@types/node": "^24.13.3",
|
|
101
|
-
"clean-publish": "^6.0.5",
|
|
102
|
-
"phaser": "4.0.0",
|
|
103
|
-
"tsup": "^8.5.1",
|
|
104
|
-
"typescript": "^5.9.3"
|
|
105
|
-
},
|
|
106
85
|
"publishConfig": {
|
|
107
86
|
"registry": "https://registry.npmjs.org/",
|
|
108
87
|
"access": "public"
|
|
109
|
-
},
|
|
110
|
-
"clean-publish": {
|
|
111
|
-
"fields": [
|
|
112
|
-
"devDependencies",
|
|
113
|
-
"scripts"
|
|
114
|
-
]
|
|
115
88
|
}
|
|
116
89
|
}
|