miaoda-game-devkit 0.2.7 → 0.2.9
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 +18 -12
- package/dist/{gameplay-audit-CusQfLYQ.d.mts → gameplay-audit-CCJo4Qhk.d.mts} +2 -2
- package/dist/{gameplay-audit-CusQfLYQ.d.ts → gameplay-audit-CCJo4Qhk.d.ts} +2 -2
- package/dist/index.d.mts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +52 -0
- package/dist/index.mjs +49 -0
- package/dist/lint/game-telemetry.contract.mjs +1241 -0
- package/dist/lint/gameplay-audit.contract.mjs +138 -49
- package/dist/lint/gameplay-contract.contract.mjs +5 -2
- package/dist/lint/vitest-config.contract.mjs +72 -35
- package/dist/vitest-config.d.mts +2 -2
- package/dist/vitest-config.d.ts +2 -2
- package/dist/vitest-config.js +72 -35
- package/dist/vitest-config.mjs +72 -35
- package/package.json +1 -1
|
@@ -45,6 +45,7 @@ function createGameplayContractMetadata(contract) {
|
|
|
45
45
|
}
|
|
46
46
|
function validateGameplayAuditOptions(options2, projectRoot) {
|
|
47
47
|
const issues = [];
|
|
48
|
+
const contracts = options2.contracts;
|
|
48
49
|
const scenes = [...new Set(options2.scenes.map((scene) => scene.trim()).filter(Boolean))];
|
|
49
50
|
const mode = options2.mode ?? "interactive";
|
|
50
51
|
if (mode === "non-interactive") {
|
|
@@ -66,7 +67,7 @@ function validateGameplayAuditOptions(options2, projectRoot) {
|
|
|
66
67
|
const coveredScenes = /* @__PURE__ */ new Set();
|
|
67
68
|
const kinds = /* @__PURE__ */ new Map();
|
|
68
69
|
const invalidKindContracts = [];
|
|
69
|
-
for (const contract of
|
|
70
|
+
for (const contract of contracts ?? []) {
|
|
70
71
|
const id = contract.id.trim();
|
|
71
72
|
if (!id) {
|
|
72
73
|
issues.push("gameplayAudit.contracts \u4E2D\u5B58\u5728\u7A7A contract id\u3002");
|
|
@@ -92,7 +93,6 @@ function validateGameplayAuditOptions(options2, projectRoot) {
|
|
|
92
93
|
kinds.set(contract.kind, list);
|
|
93
94
|
}
|
|
94
95
|
if (contract.kind === "playthrough") {
|
|
95
|
-
if (!contract.requireInput) issues.push(`playthrough contract "${id}" \u5FC5\u987B requireInput: true\u3002`);
|
|
96
96
|
if (!contract.requireFrameAdvance) issues.push(`playthrough contract "${id}" \u5FC5\u987B requireFrameAdvance: true\u3002`);
|
|
97
97
|
if (normalizeList(contract.requireCheckpoint).length === 0) issues.push(`playthrough contract "${id}" \u5FC5\u987B\u58F0\u660E requireCheckpoint\u3002`);
|
|
98
98
|
}
|
|
@@ -100,7 +100,6 @@ function validateGameplayAuditOptions(options2, projectRoot) {
|
|
|
100
100
|
issues.push(`transition contract "${id}" \u5FC5\u987B\u58F0\u660E requireTransition\u3002`);
|
|
101
101
|
}
|
|
102
102
|
if (contract.kind === "recovery") {
|
|
103
|
-
if (!contract.requireInput) issues.push(`recovery contract "${id}" \u5FC5\u987B requireInput: true\u3002`);
|
|
104
103
|
if (!contract.requireFrameAdvance) issues.push(`recovery contract "${id}" \u5FC5\u987B requireFrameAdvance: true\u3002`);
|
|
105
104
|
if (normalizeList(contract.requireRestart).length === 0) issues.push(`recovery contract "${id}" \u5FC5\u987B\u58F0\u660E requireRestart\u3002`);
|
|
106
105
|
if (normalizeList(contract.requireCheckpoint).length < 2) issues.push(`recovery contract "${id}" \u81F3\u5C11\u9700\u8981\u4E24\u4E2A checkpoint\uFF08\u91CD\u7F6E\u524D\u540E\uFF09\u3002`);
|
|
@@ -122,31 +121,24 @@ function validateGameplayAuditOptions(options2, projectRoot) {
|
|
|
122
121
|
`\u4EE5\u4E0B contract \u7F3A\u5C11\u6709\u6548 kind\uFF1A${invalidKindContracts.map((id) => `"${id}"`).join("\u3001")}\u3002\u8BF7\u4E3A\u6BCF\u4E2A contract \u8BBE\u7F6E kind: "boot"\u3001"playthrough"\u3001"transition" \u6216 "recovery"\u3002`
|
|
123
122
|
);
|
|
124
123
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
if (contracts) {
|
|
125
|
+
for (const scene of scenes) {
|
|
126
|
+
if (!coveredScenes.has(scene)) {
|
|
127
|
+
issues.push(`Scene "${scene}" \u6CA1\u6709\u4EFB\u4F55 gameplay contract \u8D1F\u8D23\u9A8C\u8BC1\u3002`);
|
|
128
|
+
}
|
|
128
129
|
}
|
|
130
|
+
if (contracts.length === 0)
|
|
131
|
+
issues.push("gameplayAudit.contracts \u5B58\u5728\u65F6\u4E0D\u80FD\u4E3A\u7A7A\uFF1B\u4E0D\u4F7F\u7528\u663E\u5F0F\u6E05\u5355\u8BF7\u7701\u7565\u8BE5\u5B57\u6BB5\u3002");
|
|
129
132
|
}
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const hasInput =
|
|
135
|
-
|
|
136
|
-
if (!hasPlaythrough || !hasInput || !hasCheckpoint) {
|
|
133
|
+
if (mode === "interactive" && contracts) {
|
|
134
|
+
const hasPlaythrough = (kinds.get("playthrough") ?? []).some(
|
|
135
|
+
(contract) => Boolean(contract.requireFrameAdvance) && normalizeList(contract.requireCheckpoint).length > 0
|
|
136
|
+
);
|
|
137
|
+
const hasInput = contracts.some((contract) => contract.requireInput);
|
|
138
|
+
if (!hasPlaythrough || !hasInput) {
|
|
137
139
|
issues.push(
|
|
138
|
-
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
const entryScene = scenes[0];
|
|
142
|
-
for (const scene of scenes.slice(1)) {
|
|
143
|
-
const transitioned = options2.contracts.some(
|
|
144
|
-
(contract) => (contract.kind === "transition" || contract.kind === "playthrough") && normalizeList(contract.requireTransition).includes(scene)
|
|
140
|
+
"\u4EA4\u4E92\u9879\u76EE\u7F3A\u5C11\u6700\u4F4E\u73A9\u6CD5\u57FA\u7EBF\uFF1A\u81F3\u5C11\u4E00\u4E2A playthrough contract \u5FC5\u987B\u58F0\u660E requireFrameAdvance \u548C checkpoint\uFF0C\u4E14\u81F3\u5C11\u4E00\u4E2A contract \u5FC5\u987B\u58F0\u660E requireInput: true\u3002"
|
|
145
141
|
);
|
|
146
|
-
if (!transitioned) issues.push(`\u975E\u5165\u53E3 Scene "${scene}" \u5FC5\u987B\u7531 transition/playthrough contract \u901A\u8FC7\u751F\u4EA7\u8F6C\u573A\u8986\u76D6\u3002`);
|
|
147
|
-
}
|
|
148
|
-
if (entryScene && scenes.length > 1 && (kinds.get("transition") ?? []).length === 0 && (kinds.get("playthrough") ?? []).length === 0 && invalidKindContracts.length === 0) {
|
|
149
|
-
issues.push(`\u4EA4\u4E92\u9879\u76EE\u5165\u53E3 Scene "${entryScene}" \u7F3A\u5C11 transition/playthrough flow\u3002`);
|
|
150
142
|
}
|
|
151
143
|
const restartSignal = projectRoot ? containsProductionRestart(projectRoot) : false;
|
|
152
144
|
if (restartSignal && options2.flows?.restart !== true) {
|
|
@@ -208,11 +200,21 @@ function getDefinitionIssues(expected, actual) {
|
|
|
208
200
|
}
|
|
209
201
|
function auditGameplayCollection(options2, file, tests) {
|
|
210
202
|
const normalizedFile = file.replaceAll("\\", "/");
|
|
211
|
-
const expected = options2.contracts.filter(
|
|
203
|
+
const expected = (options2.contracts ?? []).filter(
|
|
212
204
|
(contract) => contract.testFile.replaceAll("\\", "/") === normalizedFile
|
|
213
205
|
);
|
|
214
206
|
const actual = tests.filter((test) => test.metadata);
|
|
215
207
|
const issues = [];
|
|
208
|
+
if (!options2.contracts) {
|
|
209
|
+
const actualIds = /* @__PURE__ */ new Set();
|
|
210
|
+
for (const test of actual) {
|
|
211
|
+
const id = test.metadata?.id;
|
|
212
|
+
if (!id) continue;
|
|
213
|
+
if (actualIds.has(id)) issues.push(`contract "${id}" \u5728 ${normalizedFile} \u4E2D\u91CD\u590D\u5B9A\u4E49\u3002`);
|
|
214
|
+
actualIds.add(id);
|
|
215
|
+
}
|
|
216
|
+
return { passed: issues.length === 0, issues };
|
|
217
|
+
}
|
|
216
218
|
for (const contract of expected) {
|
|
217
219
|
const matches = actual.filter((test) => test.metadata?.id === contract.id);
|
|
218
220
|
if (matches.length === 0) {
|
|
@@ -285,7 +287,25 @@ function auditGameplayRun(options2, tests) {
|
|
|
285
287
|
const issues = [];
|
|
286
288
|
const actualContracts = tests.filter((test) => test.metadata);
|
|
287
289
|
const observedEvidence = [];
|
|
288
|
-
|
|
290
|
+
const discoveredContracts = actualContracts.flatMap((test) => {
|
|
291
|
+
const metadata = test.metadata;
|
|
292
|
+
if (!metadata) return [];
|
|
293
|
+
return [{
|
|
294
|
+
id: metadata.id,
|
|
295
|
+
kind: metadata.kind,
|
|
296
|
+
testFile: test.file,
|
|
297
|
+
scenes: metadata.scenes,
|
|
298
|
+
...metadata.requirements
|
|
299
|
+
}];
|
|
300
|
+
});
|
|
301
|
+
const expectedContracts = options2.contracts ?? discoveredContracts;
|
|
302
|
+
if (!options2.contracts) {
|
|
303
|
+
const discoveredIssues = validateGameplayAuditOptions(
|
|
304
|
+
{ ...options2, contracts: discoveredContracts }
|
|
305
|
+
);
|
|
306
|
+
issues.push(...discoveredIssues);
|
|
307
|
+
}
|
|
308
|
+
for (const expected of expectedContracts) {
|
|
289
309
|
const matches = actualContracts.filter(
|
|
290
310
|
(test2) => test2.metadata?.id === expected.id
|
|
291
311
|
);
|
|
@@ -308,7 +328,11 @@ function auditGameplayRun(options2, tests) {
|
|
|
308
328
|
}
|
|
309
329
|
issues.push(...getDefinitionIssues(expected, metadata));
|
|
310
330
|
if (test.state !== "passed") {
|
|
311
|
-
|
|
331
|
+
if (test.error) {
|
|
332
|
+
issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u6D4B\u8BD5\u5931\u8D25\uFF1A${test.error}`);
|
|
333
|
+
} else {
|
|
334
|
+
issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
|
|
335
|
+
}
|
|
312
336
|
continue;
|
|
313
337
|
}
|
|
314
338
|
if (!metadata.verified || !metadata.evidence) {
|
|
@@ -324,7 +348,7 @@ function auditGameplayRun(options2, tests) {
|
|
|
324
348
|
}
|
|
325
349
|
for (const test of actualContracts) {
|
|
326
350
|
const id = test.metadata?.id;
|
|
327
|
-
if (id && !options2.contracts.some((contract) => contract.id === id)) {
|
|
351
|
+
if (id && options2.contracts && !options2.contracts.some((contract) => contract.id === id)) {
|
|
328
352
|
issues.push(
|
|
329
353
|
`\u53D1\u73B0\u672A\u5217\u5165 gameplayAudit \u6E05\u5355\u7684 contract "${id}"\uFF08${test.file}\uFF09\u3002`
|
|
330
354
|
);
|
|
@@ -383,7 +407,8 @@ function formatGameplayIssues(issues) {
|
|
|
383
407
|
destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
|
|
384
408
|
registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4\u6E38\u620F\u6E90\u7801\u6CE8\u518C\u4E86\u8BE5 Scene\uFF1B\u6D4B\u8BD5\u9700\u8981\u5207\u6362\u5230\u5B83\u65F6\uFF0C\u5728\u521B\u5EFA host \u65F6\u901A\u8FC7 additionalScenes \u63D0\u4F9B Scene \u7C7B\u3002",
|
|
385
409
|
missing: "\u6253\u5F00\u4E0A\u8FF0\u6587\u4EF6\uFF0C\u7528 gameplayTest \u6DFB\u52A0\u8FD9\u4E9B\u73A9\u6CD5\u6D4B\u8BD5\uFF1Bid \u5FC5\u987B\u4E0E vitest.config.ts \u4E2D\u7684\u914D\u7F6E\u5B8C\u5168\u4E00\u81F4\uFF0C\u521B\u5EFA host \u540E\u8C03\u7528 contract.attachHost(host)\u3002",
|
|
386
|
-
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002"
|
|
410
|
+
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002",
|
|
411
|
+
unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
|
|
387
412
|
};
|
|
388
413
|
const getCategory = (issue) => {
|
|
389
414
|
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
@@ -408,6 +433,14 @@ function formatGameplayIssues(issues) {
|
|
|
408
433
|
return { category: "registry", key: `registry:${scene}` };
|
|
409
434
|
}
|
|
410
435
|
if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
|
|
436
|
+
if (issue.includes("\u6D4B\u8BD5\u5931\u8D25\uFF1A")) {
|
|
437
|
+
const message = issue.split("\u6D4B\u8BD5\u5931\u8D25\uFF1A")[1] ?? issue;
|
|
438
|
+
return { category: "test", key: `test:${message}` };
|
|
439
|
+
}
|
|
440
|
+
if (issue.includes("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")) {
|
|
441
|
+
const message = issue.split("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")[1] ?? issue;
|
|
442
|
+
return { category: "unhandled", key: `test:${message}` };
|
|
443
|
+
}
|
|
411
444
|
if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
|
|
412
445
|
return { category: "other", key: `other:${issue}` };
|
|
413
446
|
};
|
|
@@ -431,7 +464,7 @@ function formatGameplayIssues(issues) {
|
|
|
431
464
|
continue;
|
|
432
465
|
}
|
|
433
466
|
const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u73A9\u6CD5\u6D4B\u8BD5 "$1"').replaceAll("evidence requirements", "\u8981\u6C42\u9A8C\u8BC1\u7684\u6E38\u620F\u884C\u4E3A").replaceAll("metadata", "\u6D4B\u8BD5\u914D\u7F6E").replaceAll("host registry", "\u6D4B\u8BD5\u8BB0\u5F55\u7684 Scene \u5217\u8868");
|
|
434
|
-
const id = compact.match(/contract "([^"]+)"/)?.[1];
|
|
467
|
+
const id = compact.match(/(?:contract|玩法测试) "([^"]+)"/)?.[1];
|
|
435
468
|
const { category, key } = getCategory(compact);
|
|
436
469
|
const group = groups.get(key);
|
|
437
470
|
if (group) {
|
|
@@ -518,7 +551,7 @@ describe("gameplay audit", () => {
|
|
|
518
551
|
]
|
|
519
552
|
});
|
|
520
553
|
expect(result).toContain(
|
|
521
|
-
|
|
554
|
+
"\u4EA4\u4E92\u9879\u76EE\u7F3A\u5C11\u6700\u4F4E\u73A9\u6CD5\u57FA\u7EBF\uFF1A\u81F3\u5C11\u4E00\u4E2A playthrough contract \u5FC5\u987B\u58F0\u660E requireFrameAdvance \u548C checkpoint\uFF0C\u4E14\u81F3\u5C11\u4E00\u4E2A contract \u5FC5\u987B\u58F0\u660E requireInput: true\u3002"
|
|
522
555
|
);
|
|
523
556
|
});
|
|
524
557
|
it("\u5141\u8BB8\u5E26\u7406\u7531\u7684\u975E\u4EA4\u4E92 boot-only \u9879\u76EE", () => {
|
|
@@ -538,6 +571,44 @@ describe("gameplay audit", () => {
|
|
|
538
571
|
})
|
|
539
572
|
).toEqual([]);
|
|
540
573
|
});
|
|
574
|
+
it("\u5141\u8BB8 Telemetry \u957F\u6D41\u7A0B\u4E0E\u72EC\u7ACB\u771F\u5B9E\u8F93\u5165 contract \u5206\u5DE5", () => {
|
|
575
|
+
expect(
|
|
576
|
+
validateGameplayAuditOptions({
|
|
577
|
+
mode: "interactive",
|
|
578
|
+
scenes: ["IndexScene"],
|
|
579
|
+
contracts: [
|
|
580
|
+
{
|
|
581
|
+
id: "IndexScene.telemetry-playthrough",
|
|
582
|
+
kind: "playthrough",
|
|
583
|
+
testFile: "tests/gameplay.test.ts",
|
|
584
|
+
scenes: ["IndexScene"],
|
|
585
|
+
requireFrameAdvance: true,
|
|
586
|
+
requireCheckpoint: "progress"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
id: "IndexScene.input-smoke",
|
|
590
|
+
kind: "playthrough",
|
|
591
|
+
testFile: "tests/gameplay.test.ts",
|
|
592
|
+
scenes: ["IndexScene"],
|
|
593
|
+
requireInput: true,
|
|
594
|
+
requireFrameAdvance: true,
|
|
595
|
+
requireCheckpoint: "input-wired"
|
|
596
|
+
}
|
|
597
|
+
]
|
|
598
|
+
})
|
|
599
|
+
).toEqual([]);
|
|
600
|
+
});
|
|
601
|
+
it("\u5141\u8BB8\u7701\u7565\u91CD\u590D inventory \u5E76\u4ECE gameplayTest metadata \u5BA1\u8BA1", () => {
|
|
602
|
+
const inventoryFree = {
|
|
603
|
+
mode: "interactive",
|
|
604
|
+
scenes: ["IndexScene", "BattleScene"]
|
|
605
|
+
};
|
|
606
|
+
expect(validateGameplayAuditOptions(inventoryFree)).toEqual([]);
|
|
607
|
+
expect(auditGameplayRun(inventoryFree, [createPassingTest()])).toEqual({
|
|
608
|
+
passed: true,
|
|
609
|
+
issues: []
|
|
610
|
+
});
|
|
611
|
+
});
|
|
541
612
|
it("\u5F3A\u5236 recovery contract \u540C\u65F6\u58F0\u660E restart \u548C\u4E24\u4E2A checkpoint", () => {
|
|
542
613
|
const result = validateGameplayAuditOptions({
|
|
543
614
|
mode: "non-interactive",
|
|
@@ -557,26 +628,34 @@ describe("gameplay audit", () => {
|
|
|
557
628
|
'recovery contract "BattleScene.recovery" \u81F3\u5C11\u9700\u8981\u4E24\u4E2A checkpoint\uFF08\u91CD\u7F6E\u524D\u540E\uFF09\u3002'
|
|
558
629
|
]));
|
|
559
630
|
});
|
|
560
|
-
it("\
|
|
631
|
+
it("\u5141\u8BB8\u72EC\u7ACB\u8986\u76D6 Scene\uFF0C\u800C\u4E0D\u5F3A\u5236\u5236\u9020\u751F\u4EA7\u8F6C\u573A", () => {
|
|
561
632
|
const result = validateGameplayAuditOptions({
|
|
562
633
|
mode: "interactive",
|
|
563
634
|
scenes: ["MenuScene", "BattleScene"],
|
|
564
|
-
contracts: [
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
635
|
+
contracts: [
|
|
636
|
+
{
|
|
637
|
+
id: "MenuScene.playthrough",
|
|
638
|
+
kind: "playthrough",
|
|
639
|
+
testFile: "tests/gameplay.test.ts",
|
|
640
|
+
scenes: ["MenuScene"],
|
|
641
|
+
requireInput: true,
|
|
642
|
+
requireFrameAdvance: true,
|
|
643
|
+
requireCheckpoint: "progress"
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
id: "BattleScene.boot",
|
|
647
|
+
kind: "boot",
|
|
648
|
+
testFile: "tests/gameplay.test.ts",
|
|
649
|
+
scenes: ["BattleScene"]
|
|
650
|
+
}
|
|
651
|
+
]
|
|
573
652
|
});
|
|
574
|
-
expect(result).
|
|
653
|
+
expect(result).toEqual([]);
|
|
575
654
|
});
|
|
576
|
-
it("\
|
|
655
|
+
it("\u4ECD\u8981\u6C42 transition contract \u58F0\u660E\u76EE\u6807", () => {
|
|
577
656
|
const result = validateGameplayAuditOptions({
|
|
578
657
|
mode: "interactive",
|
|
579
|
-
scenes: ["MenuScene"
|
|
658
|
+
scenes: ["MenuScene"],
|
|
580
659
|
contracts: [
|
|
581
660
|
{
|
|
582
661
|
id: "MenuScene.playthrough",
|
|
@@ -588,16 +667,15 @@ describe("gameplay audit", () => {
|
|
|
588
667
|
requireCheckpoint: "progress"
|
|
589
668
|
},
|
|
590
669
|
{
|
|
591
|
-
id: "
|
|
592
|
-
kind: "
|
|
670
|
+
id: "MenuScene.transition",
|
|
671
|
+
kind: "transition",
|
|
593
672
|
testFile: "tests/gameplay.test.ts",
|
|
594
|
-
scenes: ["
|
|
595
|
-
requireTransition: "BattleScene"
|
|
673
|
+
scenes: ["MenuScene"]
|
|
596
674
|
}
|
|
597
675
|
]
|
|
598
676
|
});
|
|
599
677
|
expect(result).toContain(
|
|
600
|
-
'
|
|
678
|
+
'transition contract "MenuScene.transition" \u5FC5\u987B\u58F0\u660E requireTransition\u3002'
|
|
601
679
|
);
|
|
602
680
|
});
|
|
603
681
|
it("\u58F0\u660E restart flow \u540E\u4ECD\u5F3A\u5236 recovery contract", () => {
|
|
@@ -817,4 +895,15 @@ describe("gameplay audit", () => {
|
|
|
817
895
|
expect(result.repairs).toHaveLength(1);
|
|
818
896
|
expect(result.repairs[0]).toContain("vitest.config.ts");
|
|
819
897
|
});
|
|
898
|
+
it("\u4FDD\u7559\u5E76\u5408\u5E76 Vitest \u8FD0\u884C\u65F6\u5F02\u5E38\u6D88\u606F", () => {
|
|
899
|
+
const result = formatGameplayIssues([
|
|
900
|
+
'\u73A9\u6CD5\u6D4B\u8BD5 "Map.transition" \u6D4B\u8BD5\u5931\u8D25\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events',
|
|
901
|
+
'\u73A9\u6CD5\u6D4B\u8BD5 "Editor.transition" \u6D4B\u8BD5\u5931\u8D25\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events',
|
|
902
|
+
"\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events"
|
|
903
|
+
]);
|
|
904
|
+
expect(result.issues).toEqual([
|
|
905
|
+
'\u73A9\u6CD5\u6D4B\u8BD5 "Map.transition" \u6D4B\u8BD5\u5931\u8D25\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events\uFF08\u540C\u7C7B\u95EE\u9898\u8FD8\u51FA\u73B0\u5728\uFF1AEditor.transition\uFF09'
|
|
906
|
+
]);
|
|
907
|
+
expect(result.repairs[0]).toContain("\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF");
|
|
908
|
+
});
|
|
820
909
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/lint/gameplay-contract.test.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as Phaser5 from "phaser";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
4
|
|
|
5
5
|
// src/phaser-headless-host.ts
|
|
@@ -1168,8 +1168,11 @@ function registerGameplayContract(context, options) {
|
|
|
1168
1168
|
};
|
|
1169
1169
|
}
|
|
1170
1170
|
|
|
1171
|
+
// src/game-telemetry.ts
|
|
1172
|
+
import * as Phaser4 from "phaser";
|
|
1173
|
+
|
|
1171
1174
|
// src/lint/gameplay-contract.test.ts
|
|
1172
|
-
var InputScene = class extends
|
|
1175
|
+
var InputScene = class extends Phaser5.Scene {
|
|
1173
1176
|
hits = 0;
|
|
1174
1177
|
constructor() {
|
|
1175
1178
|
super("InputScene");
|
|
@@ -50,6 +50,7 @@ function createGameplayContractMetadata(contract) {
|
|
|
50
50
|
}
|
|
51
51
|
function validateGameplayAuditOptions(options, projectRoot2) {
|
|
52
52
|
const issues = [];
|
|
53
|
+
const contracts = options.contracts;
|
|
53
54
|
const scenes = [...new Set(options.scenes.map((scene) => scene.trim()).filter(Boolean))];
|
|
54
55
|
const mode = options.mode ?? "interactive";
|
|
55
56
|
if (mode === "non-interactive") {
|
|
@@ -71,7 +72,7 @@ function validateGameplayAuditOptions(options, projectRoot2) {
|
|
|
71
72
|
const coveredScenes = /* @__PURE__ */ new Set();
|
|
72
73
|
const kinds = /* @__PURE__ */ new Map();
|
|
73
74
|
const invalidKindContracts = [];
|
|
74
|
-
for (const contract of
|
|
75
|
+
for (const contract of contracts ?? []) {
|
|
75
76
|
const id = contract.id.trim();
|
|
76
77
|
if (!id) {
|
|
77
78
|
issues.push("gameplayAudit.contracts \u4E2D\u5B58\u5728\u7A7A contract id\u3002");
|
|
@@ -97,7 +98,6 @@ function validateGameplayAuditOptions(options, projectRoot2) {
|
|
|
97
98
|
kinds.set(contract.kind, list);
|
|
98
99
|
}
|
|
99
100
|
if (contract.kind === "playthrough") {
|
|
100
|
-
if (!contract.requireInput) issues.push(`playthrough contract "${id}" \u5FC5\u987B requireInput: true\u3002`);
|
|
101
101
|
if (!contract.requireFrameAdvance) issues.push(`playthrough contract "${id}" \u5FC5\u987B requireFrameAdvance: true\u3002`);
|
|
102
102
|
if (normalizeList(contract.requireCheckpoint).length === 0) issues.push(`playthrough contract "${id}" \u5FC5\u987B\u58F0\u660E requireCheckpoint\u3002`);
|
|
103
103
|
}
|
|
@@ -105,7 +105,6 @@ function validateGameplayAuditOptions(options, projectRoot2) {
|
|
|
105
105
|
issues.push(`transition contract "${id}" \u5FC5\u987B\u58F0\u660E requireTransition\u3002`);
|
|
106
106
|
}
|
|
107
107
|
if (contract.kind === "recovery") {
|
|
108
|
-
if (!contract.requireInput) issues.push(`recovery contract "${id}" \u5FC5\u987B requireInput: true\u3002`);
|
|
109
108
|
if (!contract.requireFrameAdvance) issues.push(`recovery contract "${id}" \u5FC5\u987B requireFrameAdvance: true\u3002`);
|
|
110
109
|
if (normalizeList(contract.requireRestart).length === 0) issues.push(`recovery contract "${id}" \u5FC5\u987B\u58F0\u660E requireRestart\u3002`);
|
|
111
110
|
if (normalizeList(contract.requireCheckpoint).length < 2) issues.push(`recovery contract "${id}" \u81F3\u5C11\u9700\u8981\u4E24\u4E2A checkpoint\uFF08\u91CD\u7F6E\u524D\u540E\uFF09\u3002`);
|
|
@@ -127,31 +126,24 @@ function validateGameplayAuditOptions(options, projectRoot2) {
|
|
|
127
126
|
`\u4EE5\u4E0B contract \u7F3A\u5C11\u6709\u6548 kind\uFF1A${invalidKindContracts.map((id) => `"${id}"`).join("\u3001")}\u3002\u8BF7\u4E3A\u6BCF\u4E2A contract \u8BBE\u7F6E kind: "boot"\u3001"playthrough"\u3001"transition" \u6216 "recovery"\u3002`
|
|
128
127
|
);
|
|
129
128
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
if (contracts) {
|
|
130
|
+
for (const scene of scenes) {
|
|
131
|
+
if (!coveredScenes.has(scene)) {
|
|
132
|
+
issues.push(`Scene "${scene}" \u6CA1\u6709\u4EFB\u4F55 gameplay contract \u8D1F\u8D23\u9A8C\u8BC1\u3002`);
|
|
133
|
+
}
|
|
133
134
|
}
|
|
135
|
+
if (contracts.length === 0)
|
|
136
|
+
issues.push("gameplayAudit.contracts \u5B58\u5728\u65F6\u4E0D\u80FD\u4E3A\u7A7A\uFF1B\u4E0D\u4F7F\u7528\u663E\u5F0F\u6E05\u5355\u8BF7\u7701\u7565\u8BE5\u5B57\u6BB5\u3002");
|
|
134
137
|
}
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const hasInput =
|
|
140
|
-
|
|
141
|
-
if (!hasPlaythrough || !hasInput || !hasCheckpoint) {
|
|
138
|
+
if (mode === "interactive" && contracts) {
|
|
139
|
+
const hasPlaythrough = (kinds.get("playthrough") ?? []).some(
|
|
140
|
+
(contract) => Boolean(contract.requireFrameAdvance) && normalizeList(contract.requireCheckpoint).length > 0
|
|
141
|
+
);
|
|
142
|
+
const hasInput = contracts.some((contract) => contract.requireInput);
|
|
143
|
+
if (!hasPlaythrough || !hasInput) {
|
|
142
144
|
issues.push(
|
|
143
|
-
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
const entryScene = scenes[0];
|
|
147
|
-
for (const scene of scenes.slice(1)) {
|
|
148
|
-
const transitioned = options.contracts.some(
|
|
149
|
-
(contract) => (contract.kind === "transition" || contract.kind === "playthrough") && normalizeList(contract.requireTransition).includes(scene)
|
|
145
|
+
"\u4EA4\u4E92\u9879\u76EE\u7F3A\u5C11\u6700\u4F4E\u73A9\u6CD5\u57FA\u7EBF\uFF1A\u81F3\u5C11\u4E00\u4E2A playthrough contract \u5FC5\u987B\u58F0\u660E requireFrameAdvance \u548C checkpoint\uFF0C\u4E14\u81F3\u5C11\u4E00\u4E2A contract \u5FC5\u987B\u58F0\u660E requireInput: true\u3002"
|
|
150
146
|
);
|
|
151
|
-
if (!transitioned) issues.push(`\u975E\u5165\u53E3 Scene "${scene}" \u5FC5\u987B\u7531 transition/playthrough contract \u901A\u8FC7\u751F\u4EA7\u8F6C\u573A\u8986\u76D6\u3002`);
|
|
152
|
-
}
|
|
153
|
-
if (entryScene && scenes.length > 1 && (kinds.get("transition") ?? []).length === 0 && (kinds.get("playthrough") ?? []).length === 0 && invalidKindContracts.length === 0) {
|
|
154
|
-
issues.push(`\u4EA4\u4E92\u9879\u76EE\u5165\u53E3 Scene "${entryScene}" \u7F3A\u5C11 transition/playthrough flow\u3002`);
|
|
155
147
|
}
|
|
156
148
|
const restartSignal = projectRoot2 ? containsProductionRestart(projectRoot2) : false;
|
|
157
149
|
if (restartSignal && options.flows?.restart !== true) {
|
|
@@ -213,11 +205,21 @@ function getDefinitionIssues(expected, actual) {
|
|
|
213
205
|
}
|
|
214
206
|
function auditGameplayCollection(options, file, tests) {
|
|
215
207
|
const normalizedFile = file.replaceAll("\\", "/");
|
|
216
|
-
const expected = options.contracts.filter(
|
|
208
|
+
const expected = (options.contracts ?? []).filter(
|
|
217
209
|
(contract) => contract.testFile.replaceAll("\\", "/") === normalizedFile
|
|
218
210
|
);
|
|
219
211
|
const actual = tests.filter((test) => test.metadata);
|
|
220
212
|
const issues = [];
|
|
213
|
+
if (!options.contracts) {
|
|
214
|
+
const actualIds = /* @__PURE__ */ new Set();
|
|
215
|
+
for (const test of actual) {
|
|
216
|
+
const id = test.metadata?.id;
|
|
217
|
+
if (!id) continue;
|
|
218
|
+
if (actualIds.has(id)) issues.push(`contract "${id}" \u5728 ${normalizedFile} \u4E2D\u91CD\u590D\u5B9A\u4E49\u3002`);
|
|
219
|
+
actualIds.add(id);
|
|
220
|
+
}
|
|
221
|
+
return { passed: issues.length === 0, issues };
|
|
222
|
+
}
|
|
221
223
|
for (const contract of expected) {
|
|
222
224
|
const matches = actual.filter((test) => test.metadata?.id === contract.id);
|
|
223
225
|
if (matches.length === 0) {
|
|
@@ -290,7 +292,25 @@ function auditGameplayRun(options, tests) {
|
|
|
290
292
|
const issues = [];
|
|
291
293
|
const actualContracts = tests.filter((test) => test.metadata);
|
|
292
294
|
const observedEvidence = [];
|
|
293
|
-
|
|
295
|
+
const discoveredContracts = actualContracts.flatMap((test) => {
|
|
296
|
+
const metadata = test.metadata;
|
|
297
|
+
if (!metadata) return [];
|
|
298
|
+
return [{
|
|
299
|
+
id: metadata.id,
|
|
300
|
+
kind: metadata.kind,
|
|
301
|
+
testFile: test.file,
|
|
302
|
+
scenes: metadata.scenes,
|
|
303
|
+
...metadata.requirements
|
|
304
|
+
}];
|
|
305
|
+
});
|
|
306
|
+
const expectedContracts = options.contracts ?? discoveredContracts;
|
|
307
|
+
if (!options.contracts) {
|
|
308
|
+
const discoveredIssues = validateGameplayAuditOptions(
|
|
309
|
+
{ ...options, contracts: discoveredContracts }
|
|
310
|
+
);
|
|
311
|
+
issues.push(...discoveredIssues);
|
|
312
|
+
}
|
|
313
|
+
for (const expected of expectedContracts) {
|
|
294
314
|
const matches = actualContracts.filter(
|
|
295
315
|
(test2) => test2.metadata?.id === expected.id
|
|
296
316
|
);
|
|
@@ -313,7 +333,11 @@ function auditGameplayRun(options, tests) {
|
|
|
313
333
|
}
|
|
314
334
|
issues.push(...getDefinitionIssues(expected, metadata));
|
|
315
335
|
if (test.state !== "passed") {
|
|
316
|
-
|
|
336
|
+
if (test.error) {
|
|
337
|
+
issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u6D4B\u8BD5\u5931\u8D25\uFF1A${test.error}`);
|
|
338
|
+
} else {
|
|
339
|
+
issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
|
|
340
|
+
}
|
|
317
341
|
continue;
|
|
318
342
|
}
|
|
319
343
|
if (!metadata.verified || !metadata.evidence) {
|
|
@@ -329,7 +353,7 @@ function auditGameplayRun(options, tests) {
|
|
|
329
353
|
}
|
|
330
354
|
for (const test of actualContracts) {
|
|
331
355
|
const id = test.metadata?.id;
|
|
332
|
-
if (id && !options.contracts.some((contract) => contract.id === id)) {
|
|
356
|
+
if (id && options.contracts && !options.contracts.some((contract) => contract.id === id)) {
|
|
333
357
|
issues.push(
|
|
334
358
|
`\u53D1\u73B0\u672A\u5217\u5165 gameplayAudit \u6E05\u5355\u7684 contract "${id}"\uFF08${test.file}\uFF09\u3002`
|
|
335
359
|
);
|
|
@@ -370,11 +394,13 @@ function isGameplayContractMetadata(value) {
|
|
|
370
394
|
}
|
|
371
395
|
function toAuditTest(test) {
|
|
372
396
|
const metadata = test.meta().gameplayContract;
|
|
397
|
+
const firstError = test.result().errors?.[0];
|
|
373
398
|
return {
|
|
374
399
|
file: normalizePath(test.module.relativeModuleId),
|
|
375
400
|
name: test.fullName,
|
|
376
401
|
state: test.result().state,
|
|
377
|
-
metadata: isGameplayContractMetadata(metadata) ? metadata : void 0
|
|
402
|
+
metadata: isGameplayContractMetadata(metadata) ? metadata : void 0,
|
|
403
|
+
error: firstError?.message?.replaceAll(/\s+/g, " ").trim().slice(0, 360) || void 0
|
|
378
404
|
};
|
|
379
405
|
}
|
|
380
406
|
function formatGameplayIssues(issues) {
|
|
@@ -405,7 +431,8 @@ function formatGameplayIssues(issues) {
|
|
|
405
431
|
destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
|
|
406
432
|
registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4\u6E38\u620F\u6E90\u7801\u6CE8\u518C\u4E86\u8BE5 Scene\uFF1B\u6D4B\u8BD5\u9700\u8981\u5207\u6362\u5230\u5B83\u65F6\uFF0C\u5728\u521B\u5EFA host \u65F6\u901A\u8FC7 additionalScenes \u63D0\u4F9B Scene \u7C7B\u3002",
|
|
407
433
|
missing: "\u6253\u5F00\u4E0A\u8FF0\u6587\u4EF6\uFF0C\u7528 gameplayTest \u6DFB\u52A0\u8FD9\u4E9B\u73A9\u6CD5\u6D4B\u8BD5\uFF1Bid \u5FC5\u987B\u4E0E vitest.config.ts \u4E2D\u7684\u914D\u7F6E\u5B8C\u5168\u4E00\u81F4\uFF0C\u521B\u5EFA host \u540E\u8C03\u7528 contract.attachHost(host)\u3002",
|
|
408
|
-
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002"
|
|
434
|
+
test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002",
|
|
435
|
+
unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
|
|
409
436
|
};
|
|
410
437
|
const getCategory = (issue) => {
|
|
411
438
|
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
@@ -430,6 +457,14 @@ function formatGameplayIssues(issues) {
|
|
|
430
457
|
return { category: "registry", key: `registry:${scene}` };
|
|
431
458
|
}
|
|
432
459
|
if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
|
|
460
|
+
if (issue.includes("\u6D4B\u8BD5\u5931\u8D25\uFF1A")) {
|
|
461
|
+
const message = issue.split("\u6D4B\u8BD5\u5931\u8D25\uFF1A")[1] ?? issue;
|
|
462
|
+
return { category: "test", key: `test:${message}` };
|
|
463
|
+
}
|
|
464
|
+
if (issue.includes("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")) {
|
|
465
|
+
const message = issue.split("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")[1] ?? issue;
|
|
466
|
+
return { category: "unhandled", key: `test:${message}` };
|
|
467
|
+
}
|
|
433
468
|
if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
|
|
434
469
|
return { category: "other", key: `other:${issue}` };
|
|
435
470
|
};
|
|
@@ -453,7 +488,7 @@ function formatGameplayIssues(issues) {
|
|
|
453
488
|
continue;
|
|
454
489
|
}
|
|
455
490
|
const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u73A9\u6CD5\u6D4B\u8BD5 "$1"').replaceAll("evidence requirements", "\u8981\u6C42\u9A8C\u8BC1\u7684\u6E38\u620F\u884C\u4E3A").replaceAll("metadata", "\u6D4B\u8BD5\u914D\u7F6E").replaceAll("host registry", "\u6D4B\u8BD5\u8BB0\u5F55\u7684 Scene \u5217\u8868");
|
|
456
|
-
const id = compact.match(/contract "([^"]+)"/)?.[1];
|
|
491
|
+
const id = compact.match(/(?:contract|玩法测试) "([^"]+)"/)?.[1];
|
|
457
492
|
const { category, key } = getCategory(compact);
|
|
458
493
|
const group = groups.get(key);
|
|
459
494
|
if (group) {
|
|
@@ -513,7 +548,7 @@ var GameplayAuditReporter = class {
|
|
|
513
548
|
(specification) => normalizePath(relative(this.projectRoot, specification.moduleId))
|
|
514
549
|
)
|
|
515
550
|
);
|
|
516
|
-
for (const contract of this.options.contracts) {
|
|
551
|
+
for (const contract of this.options.contracts ?? []) {
|
|
517
552
|
const testFile = normalizePath(contract.testFile);
|
|
518
553
|
if (!scheduledFiles.has(testFile)) {
|
|
519
554
|
this.reportPreflightIssue(
|
|
@@ -530,7 +565,7 @@ var GameplayAuditReporter = class {
|
|
|
530
565
|
for (const issue of result.issues) this.reportPreflightIssue(issue);
|
|
531
566
|
}
|
|
532
567
|
/** 在测试运行结束后汇总全部 contract、Ledger 和 Scene registry,并决定命令退出码。 */
|
|
533
|
-
onTestRunEnd(testModules,
|
|
568
|
+
onTestRunEnd(testModules, unhandledErrors = [], reason = "passed") {
|
|
534
569
|
const collectionFailed = reason === "failed" && testModules.some(
|
|
535
570
|
(testModule) => testModule.state() === "failed" && [...testModule.children.allTests()].length === 0
|
|
536
571
|
);
|
|
@@ -545,9 +580,11 @@ var GameplayAuditReporter = class {
|
|
|
545
580
|
(testModule) => [...testModule.children.allTests()].map(toAuditTest)
|
|
546
581
|
);
|
|
547
582
|
const result = auditGameplayRun(this.options, tests);
|
|
583
|
+
const runtimeIssues = unhandledErrors.map((error) => error.message?.replaceAll(/\s+/g, " ").trim()).filter(Boolean).map((message) => `\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A${message}`);
|
|
548
584
|
const combinedIssues = [
|
|
549
585
|
...this.preflightIssues,
|
|
550
|
-
...result.issues
|
|
586
|
+
...result.issues,
|
|
587
|
+
...runtimeIssues
|
|
551
588
|
];
|
|
552
589
|
if (combinedIssues.length > 0) {
|
|
553
590
|
printIssues("FINAL", [...new Set(combinedIssues)]);
|
|
@@ -555,7 +592,7 @@ var GameplayAuditReporter = class {
|
|
|
555
592
|
} else {
|
|
556
593
|
console.log(
|
|
557
594
|
`
|
|
558
|
-
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${
|
|
595
|
+
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${tests.filter((test) => test.metadata).length} contracts, ${this.options.scenes.length} scenes)`
|
|
559
596
|
);
|
|
560
597
|
console.log(
|
|
561
598
|
"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"
|
package/dist/vitest-config.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
-
import { c as GameplayAuditOptions } from './gameplay-audit-
|
|
2
|
+
import { c as GameplayAuditOptions } from './gameplay-audit-CCJo4Qhk.mjs';
|
|
3
3
|
import 'phaser';
|
|
4
4
|
|
|
5
5
|
interface GameVitestConfigOptions {
|
|
6
6
|
/** 模板项目的绝对根目录,通常传入 `import.meta.dirname`。 */
|
|
7
7
|
projectRoot: string;
|
|
8
|
-
/** 测试前后都必须审计的生产 Scene
|
|
8
|
+
/** 测试前后都必须审计的生产 Scene;contracts 可省略以避免重复声明测试 metadata。 */
|
|
9
9
|
gameplayAudit: GameplayAuditOptions;
|
|
10
10
|
/** 项目需要的额外路径别名;`@` 由 devkit 统一维护。 */
|
|
11
11
|
aliases?: Record<string, string>;
|
package/dist/vitest-config.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
-
import { c as GameplayAuditOptions } from './gameplay-audit-
|
|
2
|
+
import { c as GameplayAuditOptions } from './gameplay-audit-CCJo4Qhk.js';
|
|
3
3
|
import 'phaser';
|
|
4
4
|
|
|
5
5
|
interface GameVitestConfigOptions {
|
|
6
6
|
/** 模板项目的绝对根目录,通常传入 `import.meta.dirname`。 */
|
|
7
7
|
projectRoot: string;
|
|
8
|
-
/** 测试前后都必须审计的生产 Scene
|
|
8
|
+
/** 测试前后都必须审计的生产 Scene;contracts 可省略以避免重复声明测试 metadata。 */
|
|
9
9
|
gameplayAudit: GameplayAuditOptions;
|
|
10
10
|
/** 项目需要的额外路径别名;`@` 由 devkit 统一维护。 */
|
|
11
11
|
aliases?: Record<string, string>;
|