miaoda-game-devkit 0.2.16 → 0.2.19

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 CHANGED
@@ -7,8 +7,23 @@
7
7
  - React 游戏的生产/手动时钟、Strict Mode 安全 Controller ownership、JSDOM
8
8
  Vitest 配置和自动收集运行错误的 Testing Library setup,分别从 `miaoda-game-devkit/react`、
9
9
  `miaoda-game-devkit/react/testing` 和 `miaoda-game-devkit/react/vitest-config` 导入;
10
- - React 的 `playthroughTest` 与项目级 reporter 自动验证生产入口、真实 DOM 输入、
11
- 有界推进和结果断言;聚焦运行只提示未检查,完整运行缺少有效流程时失败;
10
+ - React 的 `playthroughTest` 与项目级 reporter 自动验证独立的 entry/primary DOM
11
+ 输入、有界推进、结果断言及前后权威 snapshot 变化;聚焦运行只提示未检查,
12
+ 完整运行缺少有效流程时失败;
13
+
14
+ `playthroughTest` 的主流程回调从参数接收 `user`、`performInput`、`checkpoint`、
15
+ `stepUntil` 和绑定当前测试的 `expect`。按 entry 输入、entered snapshot、primary 输入、
16
+ 有界等待、使用回调提供的 `expect` 断言结果、
17
+ progress/terminal snapshot 的顺序记录最低证据。大型游戏仍只保留一条最短关键流程;
18
+ 分支、关卡规则和恢复清理由专门测试覆盖。不要再次调用 `userEvent.setup()`。
19
+ 时间、帧或自定义 scheduler 流程必须显式传入确定性 `step`;精确物理时间、暂停恢复
20
+ 或调度清理测试可使用 `ManualGameClock`。
21
+
22
+ Vitest 的 fake timers(包括 `vi.advanceTimersToNextFrame()`)适合在 devkit 自身的
23
+ 专门 scheduler 合约中使用,但不在所有 production playthrough 中全局启用;React
24
+ Testing Library 的 `asyncWrapper` 和 user-event 内部 timer 会因此互相影响。使用自定义
25
+ scheduler、Worker 或未通过浏览器 timer 接入的引擎时,应传显式 `step` 并通过
26
+ `diagnostics` 暴露权威状态。
12
27
 
13
28
  - React Vitest 在项目声明 Phaser 3 时使用其 browser bundle,并提供仅供模块能力探测的
14
29
  最小 Canvas 2D 边界,使 Phaser 3 代码不会阻断普通 JSDOM 测试;Storage 使用 JSDOM
@@ -33,10 +48,11 @@ Scene 生命周期。开发工具包只规定最小
33
48
  控制方法名和 Core 适配由游戏负责,controls 必须调用与生产输入共享的权威命令。Telemetry
34
49
  不会复制 `host.stepFrames()`、DOM 输入或 Scene 生命周期 API。
35
50
 
36
- `createHeadlessGame` 还记录测试期间真实发生的输入、完整帧、物理步、Scene 注册与访问、
37
- Scene 命令、restart、业务 checkpoint 和销毁。玩法测试可以调用
38
- `host.assertGameplayEvidence()` 把这些客观事实设为门禁;它不推断业务语义,
39
- authoritative state 仍必须由测试显式断言。
51
+ `createHeadlessGame` 分别记录测试请求和 Phaser 公开事件确认的执行结果。输入必须被
52
+ InputPlugin 消费,完整帧必须由活动 Scene 收到 `UPDATE`,Arcade 单步必须产生
53
+ `WORLD_STEP`,Scene 转场和 restart 必须进入目标生命周期,才能满足对应的
54
+ `host.assertGameplayEvidence()` 门禁。业务 checkpoint 和 authoritative state 仍必须由
55
+ 测试显式断言,devkit 不从引擎事件推断玩法语义。
40
56
 
41
57
  HEADLESS host 会把 Phaser 自身的 warning/error、Loader 文件失败、漏掉 `load.start()` 的队列、
42
58
  未注册 Scene 命令和可见对象的 `__MISSING` 纹理升级为测试失败。只有经过确认的引擎
@@ -2,6 +2,31 @@ import * as Phaser from 'phaser';
2
2
 
3
3
  type EngineWarningMatcher = string | RegExp;
4
4
 
5
+ type HeadlessSceneTransitionMethod = "start" | "launch" | "switch" | "sleep" | "wake";
6
+ interface HeadlessSceneTransitionEvidence {
7
+ from: string;
8
+ to: string;
9
+ method: HeadlessSceneTransitionMethod;
10
+ }
11
+ interface HeadlessEngineEvidence {
12
+ /** Phaser Scene Systems 实际发出的 START 次数。 */
13
+ sceneStarts: Record<string, number>;
14
+ /** Phaser SceneManager 完成 create 后实际发出的 CREATE 次数。 */
15
+ sceneCreates: Record<string, number>;
16
+ /** 活跃 Scene 在完整游戏帧内实际收到的 UPDATE 次数。 */
17
+ sceneUpdates: Record<string, number>;
18
+ /** Phaser Scene Systems 实际完成的 SHUTDOWN 次数。 */
19
+ sceneShutdowns: Record<string, number>;
20
+ /** Phaser InputPlugin 消费并发布的指针事件数。 */
21
+ processedPointerEvents: number;
22
+ /** Phaser 命中可交互 Game Object 后发布的输入事件数。 */
23
+ processedGameObjectEvents: number;
24
+ /** Phaser KeyboardPlugin 消费并发布的键盘事件数。 */
25
+ processedKeyboardEvents: number;
26
+ /** Arcade World 实际完成并发布的 WORLD_STEP 次数。 */
27
+ arcadeWorldSteps: number;
28
+ }
29
+
5
30
  interface HeadlessGameHost<TScene extends Phaser.Scene> {
6
31
  /** 当前隔离的 Phaser 游戏实例。 */
7
32
  game: Phaser.Game;
@@ -36,8 +61,12 @@ interface HeadlessGameHost<TScene extends Phaser.Scene> {
36
61
  interface HeadlessGameplayEvidence {
37
62
  /** 通过 host 公共 API 推进的完整 Phaser 帧数。 */
38
63
  frames: number;
64
+ /** host 推进期间至少被一个活动 Scene 实际观察到的帧数。 */
65
+ observedFrames: number;
39
66
  /** 显式执行的 Arcade Physics 单步数。 */
40
67
  physicsSteps: number;
68
+ /** 显式单步期间由 Arcade World 实际确认的物理步数。 */
69
+ observedPhysicsSteps: number;
41
70
  /** 投递到 Phaser canvas 的 DOM 鼠标事件数。 */
42
71
  mouseEvents: number;
43
72
  /** 投递到 Phaser 配置目标的 DOM 键盘事件数。 */
@@ -46,36 +75,40 @@ interface HeadlessGameplayEvidence {
46
75
  touchEvents: number;
47
76
  /** 通过输入辅助方法投递的复合 click 次数。 */
48
77
  clicks: number;
78
+ /** 同一次 DOM dispatch 内被 Phaser InputPlugin 实际消费的事件数。 */
79
+ consumedInputEvents: number;
80
+ /** Phaser 公开事件确认的生命周期、输入和物理执行证据。 */
81
+ engine: HeadlessEngineEvidence;
49
82
  /** 当前 host 注册过的全部 Scene key。 */
50
83
  registeredScenes: string[];
51
- /** 至少进入过 Active Paused 状态的 Scene key。 */
84
+ /** 实际收到过 Phaser START 生命周期事件的 Scene key。 */
52
85
  visitedScenes: string[];
53
- /** 通过 Scene Plugin 的 restart 命令重启过的 Scene key。 */
86
+ /** 通过生产 restart 命令发出过重启请求的 Scene key。 */
87
+ restartRequests: string[];
88
+ /** restart 请求后实际进入了新 START 生命周期轮次的 Scene key。 */
54
89
  restartedScenes: string[];
55
90
  /** 测试显式记录的玩法检查点。 */
56
91
  checkpoints: string[];
57
92
  /** host 是否完成了游戏销毁流程。 */
58
93
  destroyed: boolean;
59
- /** 通过 Scene Plugin 公共 API 观察到的 Scene 操作。 */
60
- transitions: ReadonlyArray<{
61
- from: string;
62
- to: string;
63
- method: "start" | "launch" | "switch" | "sleep" | "wake";
64
- }>;
94
+ /** 通过 Scene Plugin 公共 API 发出的 Scene 操作请求。 */
95
+ transitionRequests: ReadonlyArray<HeadlessSceneTransitionEvidence>;
96
+ /** 请求后由目标 Scene 生命周期事件确认完成的 Scene 操作。 */
97
+ transitions: ReadonlyArray<HeadlessSceneTransitionEvidence>;
65
98
  }
66
99
  /** 一个玩法契约可以要求 host 必须留下的客观证据。 */
67
100
  interface HeadlessGameplayEvidenceRequirements {
68
- /** 至少要求一次真实 DOM 鼠标、键盘或触摸事件。 */
101
+ /** 要求真实 DOM 输入被投递,且由 Phaser InputPlugin 实际消费。 */
69
102
  requireInput?: boolean;
70
- /** 至少要求推进一个完整 Phaser 帧。 */
103
+ /** 要求 host 推进完整帧,且至少一个 Scene 实际收到 UPDATE。 */
71
104
  requireFrameAdvance?: boolean;
72
- /** 至少要求执行一次显式 Arcade Physics 单步。 */
105
+ /** 要求显式推进 Arcade Physics,且 World 实际发布 WORLD_STEP。 */
73
106
  requirePhysicsStep?: boolean;
74
- /** 要求发生一个或多个目标 key 匹配的 Scene 转场。 */
107
+ /** 要求转场请求已由目标 Scene 的实际生命周期事件确认。 */
75
108
  requireTransition?: string | string[];
76
109
  /** 要求一个或多个 Scene 至少进入过 Active 或 Paused 状态。 */
77
110
  requireScene?: string | string[];
78
- /** 要求一个或多个 Scene 通过生产 restart 命令重启。 */
111
+ /** 要求 restart 请求后实际开始新的 Scene 生命周期轮次。 */
79
112
  requireRestart?: string | string[];
80
113
  /** 要求记录一个或多个由玩法测试定义的检查点。 */
81
114
  requireCheckpoint?: string | string[];
@@ -2,6 +2,31 @@ import * as Phaser from 'phaser';
2
2
 
3
3
  type EngineWarningMatcher = string | RegExp;
4
4
 
5
+ type HeadlessSceneTransitionMethod = "start" | "launch" | "switch" | "sleep" | "wake";
6
+ interface HeadlessSceneTransitionEvidence {
7
+ from: string;
8
+ to: string;
9
+ method: HeadlessSceneTransitionMethod;
10
+ }
11
+ interface HeadlessEngineEvidence {
12
+ /** Phaser Scene Systems 实际发出的 START 次数。 */
13
+ sceneStarts: Record<string, number>;
14
+ /** Phaser SceneManager 完成 create 后实际发出的 CREATE 次数。 */
15
+ sceneCreates: Record<string, number>;
16
+ /** 活跃 Scene 在完整游戏帧内实际收到的 UPDATE 次数。 */
17
+ sceneUpdates: Record<string, number>;
18
+ /** Phaser Scene Systems 实际完成的 SHUTDOWN 次数。 */
19
+ sceneShutdowns: Record<string, number>;
20
+ /** Phaser InputPlugin 消费并发布的指针事件数。 */
21
+ processedPointerEvents: number;
22
+ /** Phaser 命中可交互 Game Object 后发布的输入事件数。 */
23
+ processedGameObjectEvents: number;
24
+ /** Phaser KeyboardPlugin 消费并发布的键盘事件数。 */
25
+ processedKeyboardEvents: number;
26
+ /** Arcade World 实际完成并发布的 WORLD_STEP 次数。 */
27
+ arcadeWorldSteps: number;
28
+ }
29
+
5
30
  interface HeadlessGameHost<TScene extends Phaser.Scene> {
6
31
  /** 当前隔离的 Phaser 游戏实例。 */
7
32
  game: Phaser.Game;
@@ -36,8 +61,12 @@ interface HeadlessGameHost<TScene extends Phaser.Scene> {
36
61
  interface HeadlessGameplayEvidence {
37
62
  /** 通过 host 公共 API 推进的完整 Phaser 帧数。 */
38
63
  frames: number;
64
+ /** host 推进期间至少被一个活动 Scene 实际观察到的帧数。 */
65
+ observedFrames: number;
39
66
  /** 显式执行的 Arcade Physics 单步数。 */
40
67
  physicsSteps: number;
68
+ /** 显式单步期间由 Arcade World 实际确认的物理步数。 */
69
+ observedPhysicsSteps: number;
41
70
  /** 投递到 Phaser canvas 的 DOM 鼠标事件数。 */
42
71
  mouseEvents: number;
43
72
  /** 投递到 Phaser 配置目标的 DOM 键盘事件数。 */
@@ -46,36 +75,40 @@ interface HeadlessGameplayEvidence {
46
75
  touchEvents: number;
47
76
  /** 通过输入辅助方法投递的复合 click 次数。 */
48
77
  clicks: number;
78
+ /** 同一次 DOM dispatch 内被 Phaser InputPlugin 实际消费的事件数。 */
79
+ consumedInputEvents: number;
80
+ /** Phaser 公开事件确认的生命周期、输入和物理执行证据。 */
81
+ engine: HeadlessEngineEvidence;
49
82
  /** 当前 host 注册过的全部 Scene key。 */
50
83
  registeredScenes: string[];
51
- /** 至少进入过 Active Paused 状态的 Scene key。 */
84
+ /** 实际收到过 Phaser START 生命周期事件的 Scene key。 */
52
85
  visitedScenes: string[];
53
- /** 通过 Scene Plugin 的 restart 命令重启过的 Scene key。 */
86
+ /** 通过生产 restart 命令发出过重启请求的 Scene key。 */
87
+ restartRequests: string[];
88
+ /** restart 请求后实际进入了新 START 生命周期轮次的 Scene key。 */
54
89
  restartedScenes: string[];
55
90
  /** 测试显式记录的玩法检查点。 */
56
91
  checkpoints: string[];
57
92
  /** host 是否完成了游戏销毁流程。 */
58
93
  destroyed: boolean;
59
- /** 通过 Scene Plugin 公共 API 观察到的 Scene 操作。 */
60
- transitions: ReadonlyArray<{
61
- from: string;
62
- to: string;
63
- method: "start" | "launch" | "switch" | "sleep" | "wake";
64
- }>;
94
+ /** 通过 Scene Plugin 公共 API 发出的 Scene 操作请求。 */
95
+ transitionRequests: ReadonlyArray<HeadlessSceneTransitionEvidence>;
96
+ /** 请求后由目标 Scene 生命周期事件确认完成的 Scene 操作。 */
97
+ transitions: ReadonlyArray<HeadlessSceneTransitionEvidence>;
65
98
  }
66
99
  /** 一个玩法契约可以要求 host 必须留下的客观证据。 */
67
100
  interface HeadlessGameplayEvidenceRequirements {
68
- /** 至少要求一次真实 DOM 鼠标、键盘或触摸事件。 */
101
+ /** 要求真实 DOM 输入被投递,且由 Phaser InputPlugin 实际消费。 */
69
102
  requireInput?: boolean;
70
- /** 至少要求推进一个完整 Phaser 帧。 */
103
+ /** 要求 host 推进完整帧,且至少一个 Scene 实际收到 UPDATE。 */
71
104
  requireFrameAdvance?: boolean;
72
- /** 至少要求执行一次显式 Arcade Physics 单步。 */
105
+ /** 要求显式推进 Arcade Physics,且 World 实际发布 WORLD_STEP。 */
73
106
  requirePhysicsStep?: boolean;
74
- /** 要求发生一个或多个目标 key 匹配的 Scene 转场。 */
107
+ /** 要求转场请求已由目标 Scene 的实际生命周期事件确认。 */
75
108
  requireTransition?: string | string[];
76
109
  /** 要求一个或多个 Scene 至少进入过 Active 或 Paused 状态。 */
77
110
  requireScene?: string | string[];
78
- /** 要求一个或多个 Scene 通过生产 restart 命令重启。 */
111
+ /** 要求 restart 请求后实际开始新的 Scene 生命周期轮次。 */
79
112
  requireRestart?: string | string[];
80
113
  /** 要求记录一个或多个由玩法测试定义的检查点。 */
81
114
  requireCheckpoint?: string | string[];
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as GameplayContractMetadata, H as HeadlessGameHost, b as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-DmbBA0M_.mjs';
2
- export { E as EngineWarningMatcher, c as GameplayAuditContract, G as GameplayAuditOptions, d as HeadlessCanvasBounds, e as HeadlessGameOptions, f as HeadlessGameplayEvidence, g as HeadlessInputDriver, h as HeadlessInputPoint, i as HeadlessKeyboardEventOptions, j as HeadlessKeyboardInput, k as HeadlessMouseInput, l as HeadlessTouchInput, m as createHeadlessGame } from './gameplay-audit-DmbBA0M_.mjs';
1
+ import { a as GameplayContractMetadata, H as HeadlessGameHost, b as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-CKgNYw2r.mjs';
2
+ export { E as EngineWarningMatcher, c as GameplayAuditContract, G as GameplayAuditOptions, d as HeadlessCanvasBounds, e as HeadlessGameOptions, f as HeadlessGameplayEvidence, g as HeadlessInputDriver, h as HeadlessInputPoint, i as HeadlessKeyboardEventOptions, j as HeadlessKeyboardInput, k as HeadlessMouseInput, l as HeadlessTouchInput, m as createHeadlessGame } from './gameplay-audit-CKgNYw2r.mjs';
3
3
  import * as Phaser from 'phaser';
4
4
  import { TestContext, TestOptions } from 'vitest';
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as GameplayContractMetadata, H as HeadlessGameHost, b as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-DmbBA0M_.js';
2
- export { E as EngineWarningMatcher, c as GameplayAuditContract, G as GameplayAuditOptions, d as HeadlessCanvasBounds, e as HeadlessGameOptions, f as HeadlessGameplayEvidence, g as HeadlessInputDriver, h as HeadlessInputPoint, i as HeadlessKeyboardEventOptions, j as HeadlessKeyboardInput, k as HeadlessMouseInput, l as HeadlessTouchInput, m as createHeadlessGame } from './gameplay-audit-DmbBA0M_.js';
1
+ import { a as GameplayContractMetadata, H as HeadlessGameHost, b as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-CKgNYw2r.js';
2
+ export { E as EngineWarningMatcher, c as GameplayAuditContract, G as GameplayAuditOptions, d as HeadlessCanvasBounds, e as HeadlessGameOptions, f as HeadlessGameplayEvidence, g as HeadlessInputDriver, h as HeadlessInputPoint, i as HeadlessKeyboardEventOptions, j as HeadlessKeyboardInput, k as HeadlessMouseInput, l as HeadlessTouchInput, m as createHeadlessGame } from './gameplay-audit-CKgNYw2r.js';
3
3
  import * as Phaser from 'phaser';
4
4
  import { TestContext, TestOptions } from 'vitest';
5
5