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.
Files changed (40) hide show
  1. package/README.md +39 -13
  2. package/dist/cli/lint.js +32 -1
  3. package/dist/game-clock-suUidZdT.d.mts +11 -0
  4. package/dist/game-clock-suUidZdT.d.ts +11 -0
  5. package/dist/{gameplay-audit-CusQfLYQ.d.mts → gameplay-audit-BwAobjIX.d.mts} +3 -3
  6. package/dist/{gameplay-audit-CusQfLYQ.d.ts → gameplay-audit-BwAobjIX.d.ts} +3 -3
  7. package/dist/index.d.mts +26 -3
  8. package/dist/index.d.ts +26 -3
  9. package/dist/index.js +52 -0
  10. package/dist/index.mjs +49 -0
  11. package/dist/lint/game-telemetry.contract.mjs +1241 -0
  12. package/dist/lint/gameplay-audit.contract.mjs +111 -46
  13. package/dist/lint/gameplay-contract.contract.mjs +5 -2
  14. package/dist/lint/vitest-config.contract.mjs +262 -37
  15. package/dist/phaser-facade.mjs +156 -0
  16. package/dist/react/index.d.mts +13 -0
  17. package/dist/react/index.d.ts +13 -0
  18. package/dist/react/index.js +57 -0
  19. package/dist/react/index.mjs +29 -0
  20. package/dist/react/testing.d.mts +21 -0
  21. package/dist/react/testing.d.ts +21 -0
  22. package/dist/react/testing.js +82 -0
  23. package/dist/react/testing.mjs +57 -0
  24. package/dist/react/vitest-config.d.mts +14 -0
  25. package/dist/react/vitest-config.d.ts +14 -0
  26. package/dist/react/vitest-config.js +57 -0
  27. package/dist/react/vitest-config.mjs +32 -0
  28. package/dist/react/vitest-setup.d.mts +2 -0
  29. package/dist/react/vitest-setup.d.ts +2 -0
  30. package/dist/react/vitest-setup.js +35 -0
  31. package/dist/react/vitest-setup.mjs +33 -0
  32. package/dist/vite.d.mts +22 -0
  33. package/dist/vite.d.ts +22 -0
  34. package/dist/vite.js +93 -0
  35. package/dist/vite.mjs +67 -0
  36. package/dist/vitest-config.d.mts +2 -2
  37. package/dist/vitest-config.d.ts +2 -2
  38. package/dist/vitest-config.js +95 -31
  39. package/dist/vitest-config.mjs +95 -31
  40. 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 options2.contracts) {
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
- 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`);
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 (options2.contracts.length === 0)
131
- issues.push("gameplayAudit.contracts \u4E0D\u80FD\u4E3A\u7A7A\u3002");
132
- if (mode === "interactive") {
133
- const hasPlaythrough = (kinds.get("playthrough") ?? []).length > 0;
134
- const hasInput = options2.contracts.some((contract) => contract.requireInput);
135
- const hasCheckpoint = options2.contracts.some((contract) => normalizeList(contract.requireCheckpoint).length > 0);
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
- '\u4EA4\u4E92\u9879\u76EE\u7F3A\u5C11\u6700\u4F4E\u73A9\u6CD5\u57FA\u7EBF\uFF1A\u81F3\u5C11\u4E00\u4E2A kind: "playthrough" contract \u5FC5\u987B\u540C\u65F6\u58F0\u660E requireInput: true\u3001requireFrameAdvance: true \u548C requireCheckpoint: ["progress"]\u3002'
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
- for (const expected of options2.contracts) {
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
- '\u4EA4\u4E92\u9879\u76EE\u7F3A\u5C11\u6700\u4F4E\u73A9\u6CD5\u57FA\u7EBF\uFF1A\u81F3\u5C11\u4E00\u4E2A kind: "playthrough" contract \u5FC5\u987B\u540C\u65F6\u58F0\u660E requireInput: true\u3001requireFrameAdvance: true \u548C requireCheckpoint: ["progress"]\u3002'
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("\u4EA4\u4E92\u9879\u76EE\u8981\u6C42\u6BCF\u4E2A\u975E\u5165\u53E3 Scene \u6709\u751F\u4EA7\u8F6C\u573A\u8BC1\u636E\u58F0\u660E", () => {
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
- id: "MenuScene.playthrough",
579
- kind: "playthrough",
580
- testFile: "tests/gameplay.test.ts",
581
- scenes: ["MenuScene"],
582
- requireInput: true,
583
- requireFrameAdvance: true,
584
- requireCheckpoint: "progress"
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).toContain('\u975E\u5165\u53E3 Scene "BattleScene" \u5FC5\u987B\u7531 transition/playthrough contract \u901A\u8FC7\u751F\u4EA7\u8F6C\u573A\u8986\u76D6\u3002');
653
+ expect(result).toEqual([]);
588
654
  });
589
- it("\u4E0D\u63A5\u53D7 boot contract \u4F2A\u88C5\u6210\u751F\u4EA7\u8F6C\u573A", () => {
655
+ it("\u4ECD\u8981\u6C42 transition contract \u58F0\u660E\u76EE\u6807", () => {
590
656
  const result = validateGameplayAuditOptions({
591
657
  mode: "interactive",
592
- scenes: ["MenuScene", "BattleScene"],
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: "BattleScene.boot",
605
- kind: "boot",
670
+ id: "MenuScene.transition",
671
+ kind: "transition",
606
672
  testFile: "tests/gameplay.test.ts",
607
- scenes: ["BattleScene"],
608
- requireTransition: "BattleScene"
673
+ scenes: ["MenuScene"]
609
674
  }
610
675
  ]
611
676
  });
612
677
  expect(result).toContain(
613
- '\u975E\u5165\u53E3 Scene "BattleScene" \u5FC5\u987B\u7531 transition/playthrough contract \u901A\u8FC7\u751F\u4EA7\u8F6C\u573A\u8986\u76D6\u3002'
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 Phaser4 from "phaser";
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 Phaser4.Scene {
1175
+ var InputScene = class extends Phaser5.Scene {
1173
1176
  hits = 0;
1174
1177
  constructor() {
1175
1178
  super("InputScene");