miaoda-game-devkit 0.2.8 → 0.2.10
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 +39 -13
- package/dist/cli/lint.js +32 -1
- package/dist/game-clock-suUidZdT.d.mts +11 -0
- package/dist/game-clock-suUidZdT.d.ts +11 -0
- package/dist/{gameplay-audit-CusQfLYQ.d.mts → gameplay-audit-BwAobjIX.d.mts} +3 -3
- package/dist/{gameplay-audit-CusQfLYQ.d.ts → gameplay-audit-BwAobjIX.d.ts} +3 -3
- 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 +262 -37
- package/dist/phaser-facade.mjs +156 -0
- package/dist/react/index.d.mts +13 -0
- package/dist/react/index.d.ts +13 -0
- package/dist/react/index.js +57 -0
- package/dist/react/index.mjs +29 -0
- package/dist/react/testing.d.mts +21 -0
- package/dist/react/testing.d.ts +21 -0
- package/dist/react/testing.js +82 -0
- package/dist/react/testing.mjs +57 -0
- package/dist/react/vitest-config.d.mts +14 -0
- package/dist/react/vitest-config.d.ts +14 -0
- package/dist/react/vitest-config.js +57 -0
- package/dist/react/vitest-config.mjs +32 -0
- package/dist/react/vitest-setup.d.mts +2 -0
- package/dist/react/vitest-setup.d.ts +2 -0
- package/dist/react/vitest-setup.js +35 -0
- package/dist/react/vitest-setup.mjs +33 -0
- package/dist/vite.d.mts +22 -0
- package/dist/vite.d.ts +22 -0
- package/dist/vite.js +93 -0
- package/dist/vite.mjs +67 -0
- package/dist/vitest-config.d.mts +2 -2
- package/dist/vitest-config.d.ts +2 -2
- package/dist/vitest-config.js +95 -31
- package/dist/vitest-config.mjs +95 -31
- package/package.json +72 -2
|
@@ -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");
|