@mpgd/game-runtime 0.4.0 → 0.5.0

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.
@@ -1,4 +1,4 @@
1
- import type { PlatformEvidenceEnvelope, PlatformGateway, PurchaseResult } from '@mpgd/platform';
1
+ import type { PlatformEvidenceEnvelope, PlatformGateway, PlatformPurchasePresentationEvent, PurchaseResult } from '@mpgd/platform';
2
2
  import type { GameServicesRewardedAdResult } from '@mpgd/game-services/operations';
3
3
  import { type AdPlacementInput, type AdProvider, type AdShowInput, type AdShowResult } from '@mpgd/platform/ads';
4
4
  import { type ObserverErrorHandler } from '../observers.js';
@@ -44,11 +44,7 @@ export declare function createCoordinatedAdProvider(input: {
44
44
  readonly result: AdShowResult;
45
45
  }) => void | Promise<void>;
46
46
  }): CoordinatedAdProvider;
47
- export interface PurchasePresentationEvent {
48
- readonly idempotencyKey: string;
49
- readonly sequence: number;
50
- readonly state: 'open' | 'closed' | 'not-started' | 'unknown';
51
- }
47
+ export type PurchasePresentationEvent = PlatformPurchasePresentationEvent;
52
48
  export interface CoordinatedPlatformGateway extends PlatformGateway {
53
49
  dispose(): void;
54
50
  }
package/dist/ads/index.js CHANGED
@@ -475,7 +475,8 @@ export function createCoordinatedPlatformGateway(input) {
475
475
  }
476
476
  maybeDetach();
477
477
  }
478
- const unsubscribe = input.purchasePresentation?.subscribe((event) => {
478
+ const purchasePresentation = input.purchasePresentation ?? input.gateway.commerce.presentation;
479
+ const unsubscribe = purchasePresentation?.subscribe((event) => {
479
480
  observe(() => {
480
481
  const flight = purchases.get(event.idempotencyKey);
481
482
  if (flight === undefined || !flight.called || flight.closed) {
@@ -27,6 +27,9 @@ export interface GamePlatformRuntime<T extends PlatformGateway = PlatformGateway
27
27
  readonly services: S;
28
28
  readonly actions?: GameActionCoordinator;
29
29
  createGameplayScope(): GameGameplayScope;
30
+ setLoadingProgress(progress: number): void;
31
+ /** Complete host loading before opening a play scene. Rejected starts remain blocked. */
32
+ completeLoading(): Promise<void>;
30
33
  /** Reconcile existing journal entries only; this method never opens platform UI. */
31
34
  reconcile(): Promise<void>;
32
35
  /** Game teardown; native observers and application-owned late recovery may continue. */
@@ -44,4 +47,6 @@ export declare function createGamePlatformRuntime<T extends PlatformGateway, S e
44
47
  subscribe(listener: (event: PurchasePresentationEvent) => void): () => void;
45
48
  };
46
49
  readonly onObserverError?: ObserverErrorHandler;
50
+ /** Must complete synchronously; asynchronous uploads belong at earlier checkpoints. */
51
+ readonly onExit?: () => void;
47
52
  }): GamePlatformRuntime<T, S>;
@@ -39,6 +39,47 @@ export function createGamePlatformRuntime(input) {
39
39
  let actions;
40
40
  let unsubscribeResume;
41
41
  let disposed = false;
42
+ let unsubscribeExit;
43
+ let startupBlock = input.gateway.gameLoading === undefined
44
+ ? undefined
45
+ : execution.acquireBlock({
46
+ reason: 'game-runtime:loading',
47
+ channels: ['simulation', 'gameplay-input', 'audio'],
48
+ });
49
+ let loadingCompletion;
50
+ const setLoadingProgress = (progress) => {
51
+ if (!disposed && loadingCompletion === undefined) {
52
+ observe(() => gateway.gameLoading?.setProgress(Math.max(0, Math.min(100, Number(progress) || 0))), input.onObserverError);
53
+ }
54
+ };
55
+ const completeLoading = () => {
56
+ if (disposed) {
57
+ return Promise.reject(new Error('Game runtime is disposed.'));
58
+ }
59
+ if (loadingCompletion !== undefined) {
60
+ return loadingCompletion;
61
+ }
62
+ const attempt = Promise.resolve().then(async () => {
63
+ if (disposed) {
64
+ throw new Error('Game runtime is disposed.');
65
+ }
66
+ gateway.gameLoading?.setProgress(100);
67
+ await gateway.gameLoading?.complete();
68
+ if (disposed) {
69
+ throw new Error('Game runtime was disposed during loading.');
70
+ }
71
+ const owned = startupBlock;
72
+ startupBlock = undefined;
73
+ owned?.release();
74
+ });
75
+ loadingCompletion = attempt;
76
+ void attempt.catch(() => {
77
+ if (loadingCompletion === attempt) {
78
+ loadingCompletion = undefined;
79
+ }
80
+ });
81
+ return attempt;
82
+ };
42
83
  let hostAudio;
43
84
  let unsubscribeHostAudio;
44
85
  const gameplayScopes = new Map();
@@ -108,6 +149,7 @@ export function createGamePlatformRuntime(input) {
108
149
  }, input.onObserverError);
109
150
  }
110
151
  unsubscribeActivity?.();
152
+ unsubscribeExit?.();
111
153
  gameplayScopes.clear();
112
154
  execution.destroy();
113
155
  if (unsubscribeHostAudio !== undefined) {
@@ -156,6 +198,14 @@ export function createGamePlatformRuntime(input) {
156
198
  ...(input.onObserverError === undefined ? {} : { onError: input.onObserverError }),
157
199
  });
158
200
  unsubscribeResume = gateway.lifecycle.onResume(() => { observe(reconcile, input.onObserverError); });
201
+ unsubscribeExit = gateway.lifecycle.onExit?.(() => {
202
+ observe(() => input.onExit?.(), input.onObserverError);
203
+ dispose();
204
+ });
205
+ if (disposed) {
206
+ unsubscribeExit?.();
207
+ throw new Error('Game exited during runtime initialization.');
208
+ }
159
209
  const settings = gateway.gameSettings;
160
210
  if (settings !== undefined) {
161
211
  const updateMute = (muted) => {
@@ -182,6 +232,8 @@ export function createGamePlatformRuntime(input) {
182
232
  presentation,
183
233
  services,
184
234
  createGameplayScope,
235
+ setLoadingProgress,
236
+ completeLoading,
185
237
  ...(actions === undefined ? {} : { actions }),
186
238
  reconcile,
187
239
  dispose,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/game-runtime",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Gameplay execution, scoped UI and action coordination with optional Phaser scene bindings.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -64,8 +64,8 @@
64
64
  "@types/node": "^24.0.0"
65
65
  },
66
66
  "dependencies": {
67
- "@mpgd/game-services": "0.18.1",
68
- "@mpgd/platform": "0.16.0"
67
+ "@mpgd/game-services": "0.19.0",
68
+ "@mpgd/platform": "0.17.0"
69
69
  },
70
70
  "repository": {
71
71
  "type": "git",