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
package/README.md
CHANGED
|
@@ -10,21 +10,25 @@
|
|
|
10
10
|
- 统一的 `miaoda-game-lint` 命令,聚合 TypeScript、Biome、Tailwind、
|
|
11
11
|
Oxlint 和 Phaser 通用契约检查。
|
|
12
12
|
- 统一的 `defineGameVitestConfig` 配置工厂,固定 Phaser 无头测试依赖的基础配置,
|
|
13
|
-
|
|
13
|
+
同时允许每个游戏声明 Scene 审计范围,并配置额外 alias、setup 文件和超时。
|
|
14
|
+
|
|
15
|
+
互动游戏的长流程优先使用游戏自己暴露的 `globalThis.gameTelemetry`。稳定 façade 通常调用
|
|
16
|
+
`installGameTelemetry(this.game, telemetry)` 绑定 Game 生命周期;只有刻意局部的 façade 才绑定
|
|
17
|
+
Scene 生命周期。开发工具包只规定最小
|
|
18
|
+
外壳:`version: 1`、`read.session()` 和游戏自有的 `controls`;`fixtures` 可选。快照字段、
|
|
19
|
+
控制方法名和 Core 适配由游戏负责,controls 必须调用与生产输入共享的权威命令。Telemetry
|
|
20
|
+
不会复制 `host.stepFrames()`、DOM 输入或 Scene 生命周期 API。
|
|
14
21
|
|
|
15
22
|
`createHeadlessGame` 还记录测试期间真实发生的输入、完整帧、物理步、Scene 注册与访问、
|
|
16
23
|
Scene 命令、restart、业务 checkpoint 和销毁。玩法测试可以调用
|
|
17
24
|
`host.assertGameplayEvidence()` 把这些客观事实设为门禁;它不推断业务语义,
|
|
18
25
|
authoritative state 仍必须由测试显式断言。
|
|
19
26
|
|
|
20
|
-
玩法测试应优先使用 `gameplayTest
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
都不会运行。配置通过后,`gameplayAudit` reporter 才会检查测试声明,并在结束时汇总
|
|
26
|
-
缺失、失败、证据不足、Scene 未访问和 registry 不一致。`registerGameplayContract` 保留为
|
|
27
|
-
不参加执行前预检的低层接口。
|
|
27
|
+
玩法测试应优先使用 `gameplayTest`。它是玩法 contract 的唯一权威声明,在执行阶段绑定
|
|
28
|
+
HEADLESS host,并在 `onTestFinished` 校验 Ledger。`vitest.config.ts` 默认只列生产 Scene,
|
|
29
|
+
不再复制 contract id、测试文件和证据要求。Reporter 从测试 metadata 汇总交互项目的最低
|
|
30
|
+
基线、失败、证据不足、Scene 未访问和 registry 不一致。旧项目仍可提供显式
|
|
31
|
+
`gameplayAudit.contracts` 清单,但新游戏通常不需要。
|
|
28
32
|
|
|
29
33
|
模板直接调用开发工具包提供的命令行程序:
|
|
30
34
|
|
|
@@ -51,7 +55,9 @@ monorepo 中的模板可以放置只包含 `extends: ["miaoda-game-devkit/biome"
|
|
|
51
55
|
工具包集中维护,`miaoda-game-lint` 会直接检查模板的这个配置。
|
|
52
56
|
|
|
53
57
|
具体游戏的玩法测试仍由模板使用方放在 `tests/`。运行时测试从本开发工具包
|
|
54
|
-
导入 `createHeadlessGame
|
|
58
|
+
导入 `createHeadlessGame`;有限目标游戏通过合法 Telemetry controls 走到正常成功终局,
|
|
59
|
+
无尽或沙盒游戏证明有意义的持续进展。短输入测试从入口操作走到一个主要操作,用真实 DOM
|
|
60
|
+
事件证明生产输入接线,并通过 Telemetry 断言业务状态变化。
|
|
55
61
|
同步帧逻辑使用 `stepFrames()`;`update()` 返回 Promise 时使用
|
|
56
62
|
`await stepFramesAsync()`,其他同步操作启动生命周期 Promise 后使用 `await settle()`。
|
|
57
63
|
|
|
@@ -79,13 +85,13 @@ DOM 到 Phaser 的输入、命中测试和游戏状态变化,不验证 Canvas/
|
|
|
79
85
|
模板的 `vitest.config.ts` 应从 `miaoda-game-devkit/vitest-config` 导入
|
|
80
86
|
`defineGameVitestConfig`。JSDOM 环境、Phaser 浏览器构建别名、通用 setup 和 mock 隔离
|
|
81
87
|
以及 Scene coverage 均由开发工具包统一维护。模板必须传入 `projectRoot` 和
|
|
82
|
-
`gameplayAudit
|
|
88
|
+
`gameplayAudit`;默认只列生产 Scene,关键流程及证据要求由 `gameplayTest` 自己声明一次。
|
|
83
89
|
确有需要时,可以通过 `aliases`、`additionalSetupFiles`、`testTimeout` 和 `hookTimeout`
|
|
84
90
|
增加安全的项目配置,不能覆盖测试范围、运行环境、审计 reporter 或 coverage 基线。
|
|
85
91
|
|
|
86
92
|
`gameplayTest` 会自动要求访问其声明的 Scene 并完成 host 销毁。玩法进展、恢复和终局应在
|
|
87
93
|
断言 authoritative state 后调用 `host.checkpoint("progress")`、
|
|
88
|
-
`host.checkpoint("recovery")`
|
|
94
|
+
`host.checkpoint("recovery")` 等稳定检查点,并在对应 `gameplayTest` 中通过 `requireCheckpoint` 声明。
|
|
89
95
|
运行结束会输出稳定的 `GAMEPLAY_AUDIT: ALL CONTRACTS PASSED` 或
|
|
90
96
|
`GAMEPLAY_AUDIT: ... FAILED` 标识。该标识只表示玩法证据审计;`pnpm test` 的整体结果
|
|
91
97
|
必须以最后的 `TEST_RESULT: PASS` 或 `TEST_RESULT: FAIL` 为准。失败报告按根因合并问题并
|
|
@@ -186,8 +186,8 @@ interface GameplayAuditOptions {
|
|
|
186
186
|
};
|
|
187
187
|
/** 生产 Scene registry 中预期出现的全部 Scene key。 */
|
|
188
188
|
scenes: string[];
|
|
189
|
-
/**
|
|
190
|
-
contracts
|
|
189
|
+
/** 可选的兼容清单;省略时以 gameplayTest metadata 作为唯一权威来源。 */
|
|
190
|
+
contracts?: GameplayAuditContract[];
|
|
191
191
|
}
|
|
192
192
|
/** 通过 Vitest task metadata 在 worker 与 reporter 之间传递的契约数据。 */
|
|
193
193
|
interface GameplayContractMetadata {
|
|
@@ -186,8 +186,8 @@ interface GameplayAuditOptions {
|
|
|
186
186
|
};
|
|
187
187
|
/** 生产 Scene registry 中预期出现的全部 Scene key。 */
|
|
188
188
|
scenes: string[];
|
|
189
|
-
/**
|
|
190
|
-
contracts
|
|
189
|
+
/** 可选的兼容清单;省略时以 gameplayTest metadata 作为唯一权威来源。 */
|
|
190
|
+
contracts?: GameplayAuditContract[];
|
|
191
191
|
}
|
|
192
192
|
/** 通过 Vitest task metadata 在 worker 与 reporter 之间传递的契约数据。 */
|
|
193
193
|
interface GameplayContractMetadata {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as GameplayContractMetadata, H as HeadlessGameHost, a as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-
|
|
2
|
-
export { b as GameplayAuditContract, c 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-
|
|
1
|
+
import { G as GameplayContractMetadata, H as HeadlessGameHost, a as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-CCJo4Qhk.mjs';
|
|
2
|
+
export { b as GameplayAuditContract, c 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-CCJo4Qhk.mjs';
|
|
3
3
|
import * as Phaser from 'phaser';
|
|
4
4
|
import { TestContext, TestOptions } from 'vitest';
|
|
5
5
|
|
|
@@ -68,6 +68,29 @@ declare module "vitest" {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/** The single global entry point used by game-owned HEADLESS telemetry. */
|
|
72
|
+
declare const GAME_TELEMETRY_GLOBAL: "gameTelemetry";
|
|
73
|
+
/**
|
|
74
|
+
* A game-owned observation and command boundary for deterministic runtime tests.
|
|
75
|
+
* Snapshot fields and command names intentionally remain specific to each game.
|
|
76
|
+
*/
|
|
77
|
+
interface GameTelemetry<SessionSnapshot, Controls extends object, Fixtures extends object = Record<string, never>> {
|
|
78
|
+
version: 1;
|
|
79
|
+
read: {
|
|
80
|
+
session(): SessionSnapshot;
|
|
81
|
+
};
|
|
82
|
+
controls: Controls;
|
|
83
|
+
fixtures?: Fixtures;
|
|
84
|
+
}
|
|
85
|
+
type AnyGameTelemetry = GameTelemetry<unknown, object, object>;
|
|
86
|
+
/**
|
|
87
|
+
* Install a game- or Scene-owned Telemetry handle and remove it on lifecycle exit.
|
|
88
|
+
* Game ownership survives Scene transitions; Scene ownership is useful for local facades.
|
|
89
|
+
*/
|
|
90
|
+
declare function installGameTelemetry<Telemetry extends AnyGameTelemetry>(owner: Phaser.Game | Phaser.Scene, telemetry: Telemetry): () => void;
|
|
91
|
+
/** Read the current typed game handle or fail with a direct setup diagnosis. */
|
|
92
|
+
declare function getGameTelemetry<Telemetry extends AnyGameTelemetry>(): Telemetry;
|
|
93
|
+
|
|
71
94
|
declare function assertSceneRuntimeHealth(scene: Phaser.Scene): void;
|
|
72
95
|
declare function assertSceneInteractiveHealth(scene: Phaser.Scene): void;
|
|
73
96
|
|
|
@@ -76,4 +99,4 @@ declare function collectMissingBitmapGlyphs(text: string, chars: Record<number,
|
|
|
76
99
|
/** 在文字状态损坏时抛出适合 CI 和编码智能体处理的错误。 */
|
|
77
100
|
declare function assertSceneTextHealth(scene: Phaser.Scene): void;
|
|
78
101
|
|
|
79
|
-
export { type GameplayContractHandle, GameplayContractMetadata, type GameplayContractOptions, type GameplayTestArguments, type GameplayTestContext, type GameplayTestOptions, HeadlessGameHost, HeadlessGameplayEvidenceRequirements, assertSceneInteractiveHealth, assertSceneRuntimeHealth, assertSceneTextHealth, collectMissingBitmapGlyphs, gameplayTest, registerGameplayContract };
|
|
102
|
+
export { type AnyGameTelemetry, GAME_TELEMETRY_GLOBAL, type GameTelemetry, type GameplayContractHandle, GameplayContractMetadata, type GameplayContractOptions, type GameplayTestArguments, type GameplayTestContext, type GameplayTestOptions, HeadlessGameHost, HeadlessGameplayEvidenceRequirements, assertSceneInteractiveHealth, assertSceneRuntimeHealth, assertSceneTextHealth, collectMissingBitmapGlyphs, gameplayTest, getGameTelemetry, installGameTelemetry, registerGameplayContract };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as GameplayContractMetadata, H as HeadlessGameHost, a as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-
|
|
2
|
-
export { b as GameplayAuditContract, c 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-
|
|
1
|
+
import { G as GameplayContractMetadata, H as HeadlessGameHost, a as HeadlessGameplayEvidenceRequirements } from './gameplay-audit-CCJo4Qhk.js';
|
|
2
|
+
export { b as GameplayAuditContract, c 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-CCJo4Qhk.js';
|
|
3
3
|
import * as Phaser from 'phaser';
|
|
4
4
|
import { TestContext, TestOptions } from 'vitest';
|
|
5
5
|
|
|
@@ -68,6 +68,29 @@ declare module "vitest" {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/** The single global entry point used by game-owned HEADLESS telemetry. */
|
|
72
|
+
declare const GAME_TELEMETRY_GLOBAL: "gameTelemetry";
|
|
73
|
+
/**
|
|
74
|
+
* A game-owned observation and command boundary for deterministic runtime tests.
|
|
75
|
+
* Snapshot fields and command names intentionally remain specific to each game.
|
|
76
|
+
*/
|
|
77
|
+
interface GameTelemetry<SessionSnapshot, Controls extends object, Fixtures extends object = Record<string, never>> {
|
|
78
|
+
version: 1;
|
|
79
|
+
read: {
|
|
80
|
+
session(): SessionSnapshot;
|
|
81
|
+
};
|
|
82
|
+
controls: Controls;
|
|
83
|
+
fixtures?: Fixtures;
|
|
84
|
+
}
|
|
85
|
+
type AnyGameTelemetry = GameTelemetry<unknown, object, object>;
|
|
86
|
+
/**
|
|
87
|
+
* Install a game- or Scene-owned Telemetry handle and remove it on lifecycle exit.
|
|
88
|
+
* Game ownership survives Scene transitions; Scene ownership is useful for local facades.
|
|
89
|
+
*/
|
|
90
|
+
declare function installGameTelemetry<Telemetry extends AnyGameTelemetry>(owner: Phaser.Game | Phaser.Scene, telemetry: Telemetry): () => void;
|
|
91
|
+
/** Read the current typed game handle or fail with a direct setup diagnosis. */
|
|
92
|
+
declare function getGameTelemetry<Telemetry extends AnyGameTelemetry>(): Telemetry;
|
|
93
|
+
|
|
71
94
|
declare function assertSceneRuntimeHealth(scene: Phaser.Scene): void;
|
|
72
95
|
declare function assertSceneInteractiveHealth(scene: Phaser.Scene): void;
|
|
73
96
|
|
|
@@ -76,4 +99,4 @@ declare function collectMissingBitmapGlyphs(text: string, chars: Record<number,
|
|
|
76
99
|
/** 在文字状态损坏时抛出适合 CI 和编码智能体处理的错误。 */
|
|
77
100
|
declare function assertSceneTextHealth(scene: Phaser.Scene): void;
|
|
78
101
|
|
|
79
|
-
export { type GameplayContractHandle, GameplayContractMetadata, type GameplayContractOptions, type GameplayTestArguments, type GameplayTestContext, type GameplayTestOptions, HeadlessGameHost, HeadlessGameplayEvidenceRequirements, assertSceneInteractiveHealth, assertSceneRuntimeHealth, assertSceneTextHealth, collectMissingBitmapGlyphs, gameplayTest, registerGameplayContract };
|
|
102
|
+
export { type AnyGameTelemetry, GAME_TELEMETRY_GLOBAL, type GameTelemetry, type GameplayContractHandle, GameplayContractMetadata, type GameplayContractOptions, type GameplayTestArguments, type GameplayTestContext, type GameplayTestOptions, HeadlessGameHost, HeadlessGameplayEvidenceRequirements, assertSceneInteractiveHealth, assertSceneRuntimeHealth, assertSceneTextHealth, collectMissingBitmapGlyphs, gameplayTest, getGameTelemetry, installGameTelemetry, registerGameplayContract };
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
GAME_TELEMETRY_GLOBAL: () => GAME_TELEMETRY_GLOBAL,
|
|
33
34
|
assertSceneInteractiveHealth: () => assertSceneInteractiveHealth,
|
|
34
35
|
assertSceneRuntimeHealth: () => assertSceneRuntimeHealth,
|
|
35
36
|
assertSceneTextHealth: () => assertSceneTextHealth,
|
|
36
37
|
collectMissingBitmapGlyphs: () => collectMissingBitmapGlyphs,
|
|
37
38
|
createHeadlessGame: () => createHeadlessGame,
|
|
38
39
|
gameplayTest: () => gameplayTest,
|
|
40
|
+
getGameTelemetry: () => getGameTelemetry,
|
|
41
|
+
installGameTelemetry: () => installGameTelemetry,
|
|
39
42
|
registerGameplayContract: () => registerGameplayContract
|
|
40
43
|
});
|
|
41
44
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -1215,13 +1218,62 @@ function gameplayTest(name, options, run) {
|
|
|
1215
1218
|
await run({ context, contract });
|
|
1216
1219
|
});
|
|
1217
1220
|
}
|
|
1221
|
+
|
|
1222
|
+
// src/game-telemetry.ts
|
|
1223
|
+
var Phaser4 = __toESM(require("phaser"));
|
|
1224
|
+
var GAME_TELEMETRY_GLOBAL = "gameTelemetry";
|
|
1225
|
+
function telemetryGlobal() {
|
|
1226
|
+
return globalThis;
|
|
1227
|
+
}
|
|
1228
|
+
function assertTelemetryShell(value) {
|
|
1229
|
+
if (value.version !== 1 || !value.read || typeof value.read.session !== "function" || !value.controls || typeof value.controls !== "object") {
|
|
1230
|
+
throw new Error(
|
|
1231
|
+
"Game Telemetry must provide version 1, read.session(), and a controls object."
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function installGameTelemetry(owner, telemetry) {
|
|
1236
|
+
assertTelemetryShell(telemetry);
|
|
1237
|
+
const scope = telemetryGlobal();
|
|
1238
|
+
if (scope[GAME_TELEMETRY_GLOBAL]) {
|
|
1239
|
+
throw new Error(
|
|
1240
|
+
"Only one active owner may install globalThis.gameTelemetry. Use one game-owned facade or clean up the current Scene-owned facade first."
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
const events = owner.events;
|
|
1244
|
+
const lifecycleEvents = owner instanceof Phaser4.Game ? [Phaser4.Core.Events.DESTROY] : [Phaser4.Scenes.Events.SHUTDOWN, Phaser4.Scenes.Events.DESTROY];
|
|
1245
|
+
let installed = true;
|
|
1246
|
+
const cleanup = () => {
|
|
1247
|
+
if (!installed) return;
|
|
1248
|
+
installed = false;
|
|
1249
|
+
for (const event of lifecycleEvents) events.off(event, cleanup);
|
|
1250
|
+
if (scope[GAME_TELEMETRY_GLOBAL] === telemetry) {
|
|
1251
|
+
scope[GAME_TELEMETRY_GLOBAL] = void 0;
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
scope[GAME_TELEMETRY_GLOBAL] = telemetry;
|
|
1255
|
+
for (const event of lifecycleEvents) events.once(event, cleanup);
|
|
1256
|
+
return cleanup;
|
|
1257
|
+
}
|
|
1258
|
+
function getGameTelemetry() {
|
|
1259
|
+
const telemetry = telemetryGlobal()[GAME_TELEMETRY_GLOBAL];
|
|
1260
|
+
if (!telemetry) {
|
|
1261
|
+
throw new Error(
|
|
1262
|
+
"Game Telemetry is not installed. Install it from a Scene create() method with a Game or Scene lifecycle owner."
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
return telemetry;
|
|
1266
|
+
}
|
|
1218
1267
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1219
1268
|
0 && (module.exports = {
|
|
1269
|
+
GAME_TELEMETRY_GLOBAL,
|
|
1220
1270
|
assertSceneInteractiveHealth,
|
|
1221
1271
|
assertSceneRuntimeHealth,
|
|
1222
1272
|
assertSceneTextHealth,
|
|
1223
1273
|
collectMissingBitmapGlyphs,
|
|
1224
1274
|
createHeadlessGame,
|
|
1225
1275
|
gameplayTest,
|
|
1276
|
+
getGameTelemetry,
|
|
1277
|
+
installGameTelemetry,
|
|
1226
1278
|
registerGameplayContract
|
|
1227
1279
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1173,12 +1173,61 @@ function gameplayTest(name, options, run) {
|
|
|
1173
1173
|
await run({ context, contract });
|
|
1174
1174
|
});
|
|
1175
1175
|
}
|
|
1176
|
+
|
|
1177
|
+
// src/game-telemetry.ts
|
|
1178
|
+
import * as Phaser4 from "phaser";
|
|
1179
|
+
var GAME_TELEMETRY_GLOBAL = "gameTelemetry";
|
|
1180
|
+
function telemetryGlobal() {
|
|
1181
|
+
return globalThis;
|
|
1182
|
+
}
|
|
1183
|
+
function assertTelemetryShell(value) {
|
|
1184
|
+
if (value.version !== 1 || !value.read || typeof value.read.session !== "function" || !value.controls || typeof value.controls !== "object") {
|
|
1185
|
+
throw new Error(
|
|
1186
|
+
"Game Telemetry must provide version 1, read.session(), and a controls object."
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function installGameTelemetry(owner, telemetry) {
|
|
1191
|
+
assertTelemetryShell(telemetry);
|
|
1192
|
+
const scope = telemetryGlobal();
|
|
1193
|
+
if (scope[GAME_TELEMETRY_GLOBAL]) {
|
|
1194
|
+
throw new Error(
|
|
1195
|
+
"Only one active owner may install globalThis.gameTelemetry. Use one game-owned facade or clean up the current Scene-owned facade first."
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
const events = owner.events;
|
|
1199
|
+
const lifecycleEvents = owner instanceof Phaser4.Game ? [Phaser4.Core.Events.DESTROY] : [Phaser4.Scenes.Events.SHUTDOWN, Phaser4.Scenes.Events.DESTROY];
|
|
1200
|
+
let installed = true;
|
|
1201
|
+
const cleanup = () => {
|
|
1202
|
+
if (!installed) return;
|
|
1203
|
+
installed = false;
|
|
1204
|
+
for (const event of lifecycleEvents) events.off(event, cleanup);
|
|
1205
|
+
if (scope[GAME_TELEMETRY_GLOBAL] === telemetry) {
|
|
1206
|
+
scope[GAME_TELEMETRY_GLOBAL] = void 0;
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
scope[GAME_TELEMETRY_GLOBAL] = telemetry;
|
|
1210
|
+
for (const event of lifecycleEvents) events.once(event, cleanup);
|
|
1211
|
+
return cleanup;
|
|
1212
|
+
}
|
|
1213
|
+
function getGameTelemetry() {
|
|
1214
|
+
const telemetry = telemetryGlobal()[GAME_TELEMETRY_GLOBAL];
|
|
1215
|
+
if (!telemetry) {
|
|
1216
|
+
throw new Error(
|
|
1217
|
+
"Game Telemetry is not installed. Install it from a Scene create() method with a Game or Scene lifecycle owner."
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
return telemetry;
|
|
1221
|
+
}
|
|
1176
1222
|
export {
|
|
1223
|
+
GAME_TELEMETRY_GLOBAL,
|
|
1177
1224
|
assertSceneInteractiveHealth,
|
|
1178
1225
|
assertSceneRuntimeHealth,
|
|
1179
1226
|
assertSceneTextHealth,
|
|
1180
1227
|
collectMissingBitmapGlyphs,
|
|
1181
1228
|
createHeadlessGame,
|
|
1182
1229
|
gameplayTest,
|
|
1230
|
+
getGameTelemetry,
|
|
1231
|
+
installGameTelemetry,
|
|
1183
1232
|
registerGameplayContract
|
|
1184
1233
|
};
|