miaoda-game-devkit 0.2.8 → 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 +111 -46
- package/dist/lint/gameplay-contract.contract.mjs +5 -2
- package/dist/lint/vitest-config.contract.mjs +49 -29
- package/dist/vitest-config.d.mts +2 -2
- package/dist/vitest-config.d.ts +2 -2
- package/dist/vitest-config.js +49 -29
- package/dist/vitest-config.mjs +49 -29
- 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
|
);
|
|
@@ -328,7 +348,7 @@ function auditGameplayRun(options2, tests) {
|
|
|
328
348
|
}
|
|
329
349
|
for (const test of actualContracts) {
|
|
330
350
|
const id = test.metadata?.id;
|
|
331
|
-
if (id && !options2.contracts.some((contract) => contract.id === id)) {
|
|
351
|
+
if (id && options2.contracts && !options2.contracts.some((contract) => contract.id === id)) {
|
|
332
352
|
issues.push(
|
|
333
353
|
`\u53D1\u73B0\u672A\u5217\u5165 gameplayAudit \u6E05\u5355\u7684 contract "${id}"\uFF08${test.file}\uFF09\u3002`
|
|
334
354
|
);
|
|
@@ -531,7 +551,7 @@ describe("gameplay audit", () => {
|
|
|
531
551
|
]
|
|
532
552
|
});
|
|
533
553
|
expect(result).toContain(
|
|
534
|
-
|
|
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"
|
|
535
555
|
);
|
|
536
556
|
});
|
|
537
557
|
it("\u5141\u8BB8\u5E26\u7406\u7531\u7684\u975E\u4EA4\u4E92 boot-only \u9879\u76EE", () => {
|
|
@@ -551,6 +571,44 @@ describe("gameplay audit", () => {
|
|
|
551
571
|
})
|
|
552
572
|
).toEqual([]);
|
|
553
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
|
+
});
|
|
554
612
|
it("\u5F3A\u5236 recovery contract \u540C\u65F6\u58F0\u660E restart \u548C\u4E24\u4E2A checkpoint", () => {
|
|
555
613
|
const result = validateGameplayAuditOptions({
|
|
556
614
|
mode: "non-interactive",
|
|
@@ -570,26 +628,34 @@ describe("gameplay audit", () => {
|
|
|
570
628
|
'recovery contract "BattleScene.recovery" \u81F3\u5C11\u9700\u8981\u4E24\u4E2A checkpoint\uFF08\u91CD\u7F6E\u524D\u540E\uFF09\u3002'
|
|
571
629
|
]));
|
|
572
630
|
});
|
|
573
|
-
it("\
|
|
631
|
+
it("\u5141\u8BB8\u72EC\u7ACB\u8986\u76D6 Scene\uFF0C\u800C\u4E0D\u5F3A\u5236\u5236\u9020\u751F\u4EA7\u8F6C\u573A", () => {
|
|
574
632
|
const result = validateGameplayAuditOptions({
|
|
575
633
|
mode: "interactive",
|
|
576
634
|
scenes: ["MenuScene", "BattleScene"],
|
|
577
|
-
contracts: [
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
+
]
|
|
586
652
|
});
|
|
587
|
-
expect(result).
|
|
653
|
+
expect(result).toEqual([]);
|
|
588
654
|
});
|
|
589
|
-
it("\
|
|
655
|
+
it("\u4ECD\u8981\u6C42 transition contract \u58F0\u660E\u76EE\u6807", () => {
|
|
590
656
|
const result = validateGameplayAuditOptions({
|
|
591
657
|
mode: "interactive",
|
|
592
|
-
scenes: ["MenuScene"
|
|
658
|
+
scenes: ["MenuScene"],
|
|
593
659
|
contracts: [
|
|
594
660
|
{
|
|
595
661
|
id: "MenuScene.playthrough",
|
|
@@ -601,16 +667,15 @@ describe("gameplay audit", () => {
|
|
|
601
667
|
requireCheckpoint: "progress"
|
|
602
668
|
},
|
|
603
669
|
{
|
|
604
|
-
id: "
|
|
605
|
-
kind: "
|
|
670
|
+
id: "MenuScene.transition",
|
|
671
|
+
kind: "transition",
|
|
606
672
|
testFile: "tests/gameplay.test.ts",
|
|
607
|
-
scenes: ["
|
|
608
|
-
requireTransition: "BattleScene"
|
|
673
|
+
scenes: ["MenuScene"]
|
|
609
674
|
}
|
|
610
675
|
]
|
|
611
676
|
});
|
|
612
677
|
expect(result).toContain(
|
|
613
|
-
'
|
|
678
|
+
'transition contract "MenuScene.transition" \u5FC5\u987B\u58F0\u660E requireTransition\u3002'
|
|
614
679
|
);
|
|
615
680
|
});
|
|
616
681
|
it("\u58F0\u660E restart flow \u540E\u4ECD\u5F3A\u5236 recovery contract", () => {
|
|
@@ -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,32 +126,25 @@ 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
|
-
|
|
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"
|
|
144
146
|
);
|
|
145
147
|
}
|
|
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)
|
|
150
|
-
);
|
|
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
|
-
}
|
|
156
148
|
const restartSignal = projectRoot2 ? containsProductionRestart(projectRoot2) : false;
|
|
157
149
|
if (restartSignal && options.flows?.restart !== true) {
|
|
158
150
|
issues.push("\u68C0\u6D4B\u5230\u751F\u4EA7 Scene \u4F7F\u7528 scene.restart()\uFF1B\u5FC5\u987B\u58F0\u660E flows.restart: true \u5E76\u63D0\u4F9B kind: recovery contract\u3002");
|
|
@@ -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
|
);
|
|
@@ -333,7 +353,7 @@ function auditGameplayRun(options, tests) {
|
|
|
333
353
|
}
|
|
334
354
|
for (const test of actualContracts) {
|
|
335
355
|
const id = test.metadata?.id;
|
|
336
|
-
if (id && !options.contracts.some((contract) => contract.id === id)) {
|
|
356
|
+
if (id && options.contracts && !options.contracts.some((contract) => contract.id === id)) {
|
|
337
357
|
issues.push(
|
|
338
358
|
`\u53D1\u73B0\u672A\u5217\u5165 gameplayAudit \u6E05\u5355\u7684 contract "${id}"\uFF08${test.file}\uFF09\u3002`
|
|
339
359
|
);
|
|
@@ -528,7 +548,7 @@ var GameplayAuditReporter = class {
|
|
|
528
548
|
(specification) => normalizePath(relative(this.projectRoot, specification.moduleId))
|
|
529
549
|
)
|
|
530
550
|
);
|
|
531
|
-
for (const contract of this.options.contracts) {
|
|
551
|
+
for (const contract of this.options.contracts ?? []) {
|
|
532
552
|
const testFile = normalizePath(contract.testFile);
|
|
533
553
|
if (!scheduledFiles.has(testFile)) {
|
|
534
554
|
this.reportPreflightIssue(
|
|
@@ -572,7 +592,7 @@ var GameplayAuditReporter = class {
|
|
|
572
592
|
} else {
|
|
573
593
|
console.log(
|
|
574
594
|
`
|
|
575
|
-
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${
|
|
595
|
+
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${tests.filter((test) => test.metadata).length} contracts, ${this.options.scenes.length} scenes)`
|
|
576
596
|
);
|
|
577
597
|
console.log(
|
|
578
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>;
|
package/dist/vitest-config.js
CHANGED
|
@@ -70,6 +70,7 @@ function createGameplayContractMetadata(contract) {
|
|
|
70
70
|
}
|
|
71
71
|
function validateGameplayAuditOptions(options, projectRoot) {
|
|
72
72
|
const issues = [];
|
|
73
|
+
const contracts = options.contracts;
|
|
73
74
|
const scenes = [...new Set(options.scenes.map((scene) => scene.trim()).filter(Boolean))];
|
|
74
75
|
const mode = options.mode ?? "interactive";
|
|
75
76
|
if (mode === "non-interactive") {
|
|
@@ -91,7 +92,7 @@ function validateGameplayAuditOptions(options, projectRoot) {
|
|
|
91
92
|
const coveredScenes = /* @__PURE__ */ new Set();
|
|
92
93
|
const kinds = /* @__PURE__ */ new Map();
|
|
93
94
|
const invalidKindContracts = [];
|
|
94
|
-
for (const contract of
|
|
95
|
+
for (const contract of contracts ?? []) {
|
|
95
96
|
const id = contract.id.trim();
|
|
96
97
|
if (!id) {
|
|
97
98
|
issues.push("gameplayAudit.contracts \u4E2D\u5B58\u5728\u7A7A contract id\u3002");
|
|
@@ -117,7 +118,6 @@ function validateGameplayAuditOptions(options, projectRoot) {
|
|
|
117
118
|
kinds.set(contract.kind, list);
|
|
118
119
|
}
|
|
119
120
|
if (contract.kind === "playthrough") {
|
|
120
|
-
if (!contract.requireInput) issues.push(`playthrough contract "${id}" \u5FC5\u987B requireInput: true\u3002`);
|
|
121
121
|
if (!contract.requireFrameAdvance) issues.push(`playthrough contract "${id}" \u5FC5\u987B requireFrameAdvance: true\u3002`);
|
|
122
122
|
if (normalizeList(contract.requireCheckpoint).length === 0) issues.push(`playthrough contract "${id}" \u5FC5\u987B\u58F0\u660E requireCheckpoint\u3002`);
|
|
123
123
|
}
|
|
@@ -125,7 +125,6 @@ function validateGameplayAuditOptions(options, projectRoot) {
|
|
|
125
125
|
issues.push(`transition contract "${id}" \u5FC5\u987B\u58F0\u660E requireTransition\u3002`);
|
|
126
126
|
}
|
|
127
127
|
if (contract.kind === "recovery") {
|
|
128
|
-
if (!contract.requireInput) issues.push(`recovery contract "${id}" \u5FC5\u987B requireInput: true\u3002`);
|
|
129
128
|
if (!contract.requireFrameAdvance) issues.push(`recovery contract "${id}" \u5FC5\u987B requireFrameAdvance: true\u3002`);
|
|
130
129
|
if (normalizeList(contract.requireRestart).length === 0) issues.push(`recovery contract "${id}" \u5FC5\u987B\u58F0\u660E requireRestart\u3002`);
|
|
131
130
|
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`);
|
|
@@ -147,32 +146,25 @@ function validateGameplayAuditOptions(options, projectRoot) {
|
|
|
147
146
|
`\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`
|
|
148
147
|
);
|
|
149
148
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
if (contracts) {
|
|
150
|
+
for (const scene of scenes) {
|
|
151
|
+
if (!coveredScenes.has(scene)) {
|
|
152
|
+
issues.push(`Scene "${scene}" \u6CA1\u6709\u4EFB\u4F55 gameplay contract \u8D1F\u8D23\u9A8C\u8BC1\u3002`);
|
|
153
|
+
}
|
|
153
154
|
}
|
|
155
|
+
if (contracts.length === 0)
|
|
156
|
+
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");
|
|
154
157
|
}
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const hasInput =
|
|
160
|
-
|
|
161
|
-
if (!hasPlaythrough || !hasInput || !hasCheckpoint) {
|
|
158
|
+
if (mode === "interactive" && contracts) {
|
|
159
|
+
const hasPlaythrough = (kinds.get("playthrough") ?? []).some(
|
|
160
|
+
(contract) => Boolean(contract.requireFrameAdvance) && normalizeList(contract.requireCheckpoint).length > 0
|
|
161
|
+
);
|
|
162
|
+
const hasInput = contracts.some((contract) => contract.requireInput);
|
|
163
|
+
if (!hasPlaythrough || !hasInput) {
|
|
162
164
|
issues.push(
|
|
163
|
-
|
|
165
|
+
"\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"
|
|
164
166
|
);
|
|
165
167
|
}
|
|
166
|
-
const entryScene = scenes[0];
|
|
167
|
-
for (const scene of scenes.slice(1)) {
|
|
168
|
-
const transitioned = options.contracts.some(
|
|
169
|
-
(contract) => (contract.kind === "transition" || contract.kind === "playthrough") && normalizeList(contract.requireTransition).includes(scene)
|
|
170
|
-
);
|
|
171
|
-
if (!transitioned) issues.push(`\u975E\u5165\u53E3 Scene "${scene}" \u5FC5\u987B\u7531 transition/playthrough contract \u901A\u8FC7\u751F\u4EA7\u8F6C\u573A\u8986\u76D6\u3002`);
|
|
172
|
-
}
|
|
173
|
-
if (entryScene && scenes.length > 1 && (kinds.get("transition") ?? []).length === 0 && (kinds.get("playthrough") ?? []).length === 0 && invalidKindContracts.length === 0) {
|
|
174
|
-
issues.push(`\u4EA4\u4E92\u9879\u76EE\u5165\u53E3 Scene "${entryScene}" \u7F3A\u5C11 transition/playthrough flow\u3002`);
|
|
175
|
-
}
|
|
176
168
|
const restartSignal = projectRoot ? containsProductionRestart(projectRoot) : false;
|
|
177
169
|
if (restartSignal && options.flows?.restart !== true) {
|
|
178
170
|
issues.push("\u68C0\u6D4B\u5230\u751F\u4EA7 Scene \u4F7F\u7528 scene.restart()\uFF1B\u5FC5\u987B\u58F0\u660E flows.restart: true \u5E76\u63D0\u4F9B kind: recovery contract\u3002");
|
|
@@ -233,11 +225,21 @@ function getDefinitionIssues(expected, actual) {
|
|
|
233
225
|
}
|
|
234
226
|
function auditGameplayCollection(options, file, tests) {
|
|
235
227
|
const normalizedFile = file.replaceAll("\\", "/");
|
|
236
|
-
const expected = options.contracts.filter(
|
|
228
|
+
const expected = (options.contracts ?? []).filter(
|
|
237
229
|
(contract) => contract.testFile.replaceAll("\\", "/") === normalizedFile
|
|
238
230
|
);
|
|
239
231
|
const actual = tests.filter((test) => test.metadata);
|
|
240
232
|
const issues = [];
|
|
233
|
+
if (!options.contracts) {
|
|
234
|
+
const actualIds = /* @__PURE__ */ new Set();
|
|
235
|
+
for (const test of actual) {
|
|
236
|
+
const id = test.metadata?.id;
|
|
237
|
+
if (!id) continue;
|
|
238
|
+
if (actualIds.has(id)) issues.push(`contract "${id}" \u5728 ${normalizedFile} \u4E2D\u91CD\u590D\u5B9A\u4E49\u3002`);
|
|
239
|
+
actualIds.add(id);
|
|
240
|
+
}
|
|
241
|
+
return { passed: issues.length === 0, issues };
|
|
242
|
+
}
|
|
241
243
|
for (const contract of expected) {
|
|
242
244
|
const matches = actual.filter((test) => test.metadata?.id === contract.id);
|
|
243
245
|
if (matches.length === 0) {
|
|
@@ -310,7 +312,25 @@ function auditGameplayRun(options, tests) {
|
|
|
310
312
|
const issues = [];
|
|
311
313
|
const actualContracts = tests.filter((test) => test.metadata);
|
|
312
314
|
const observedEvidence = [];
|
|
313
|
-
|
|
315
|
+
const discoveredContracts = actualContracts.flatMap((test) => {
|
|
316
|
+
const metadata = test.metadata;
|
|
317
|
+
if (!metadata) return [];
|
|
318
|
+
return [{
|
|
319
|
+
id: metadata.id,
|
|
320
|
+
kind: metadata.kind,
|
|
321
|
+
testFile: test.file,
|
|
322
|
+
scenes: metadata.scenes,
|
|
323
|
+
...metadata.requirements
|
|
324
|
+
}];
|
|
325
|
+
});
|
|
326
|
+
const expectedContracts = options.contracts ?? discoveredContracts;
|
|
327
|
+
if (!options.contracts) {
|
|
328
|
+
const discoveredIssues = validateGameplayAuditOptions(
|
|
329
|
+
{ ...options, contracts: discoveredContracts }
|
|
330
|
+
);
|
|
331
|
+
issues.push(...discoveredIssues);
|
|
332
|
+
}
|
|
333
|
+
for (const expected of expectedContracts) {
|
|
314
334
|
const matches = actualContracts.filter(
|
|
315
335
|
(test2) => test2.metadata?.id === expected.id
|
|
316
336
|
);
|
|
@@ -353,7 +373,7 @@ function auditGameplayRun(options, tests) {
|
|
|
353
373
|
}
|
|
354
374
|
for (const test of actualContracts) {
|
|
355
375
|
const id = test.metadata?.id;
|
|
356
|
-
if (id && !options.contracts.some((contract) => contract.id === id)) {
|
|
376
|
+
if (id && options.contracts && !options.contracts.some((contract) => contract.id === id)) {
|
|
357
377
|
issues.push(
|
|
358
378
|
`\u53D1\u73B0\u672A\u5217\u5165 gameplayAudit \u6E05\u5355\u7684 contract "${id}"\uFF08${test.file}\uFF09\u3002`
|
|
359
379
|
);
|
|
@@ -548,7 +568,7 @@ var GameplayAuditReporter = class {
|
|
|
548
568
|
(specification) => normalizePath((0, import_node_path2.relative)(this.projectRoot, specification.moduleId))
|
|
549
569
|
)
|
|
550
570
|
);
|
|
551
|
-
for (const contract of this.options.contracts) {
|
|
571
|
+
for (const contract of this.options.contracts ?? []) {
|
|
552
572
|
const testFile = normalizePath(contract.testFile);
|
|
553
573
|
if (!scheduledFiles.has(testFile)) {
|
|
554
574
|
this.reportPreflightIssue(
|
|
@@ -592,7 +612,7 @@ var GameplayAuditReporter = class {
|
|
|
592
612
|
} else {
|
|
593
613
|
console.log(
|
|
594
614
|
`
|
|
595
|
-
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${
|
|
615
|
+
GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${tests.filter((test) => test.metadata).length} contracts, ${this.options.scenes.length} scenes)`
|
|
596
616
|
);
|
|
597
617
|
console.log(
|
|
598
618
|
"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"
|