@mpgd/game-runtime 0.3.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.
- package/dist/ads/index.d.ts +2 -6
- package/dist/ads/index.js +5 -2
- package/dist/game/index.d.ts +11 -0
- package/dist/game/index.js +144 -1
- package/dist/phaser/index.d.ts +2 -0
- package/dist/phaser/index.js +9 -0
- package/dist/presentation/index.d.ts +1 -0
- package/dist/presentation/index.js +35 -2
- package/package.json +3 -3
package/dist/ads/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
@@ -34,6 +34,7 @@ export function createCoordinatedAdProvider(input) {
|
|
|
34
34
|
const protocol = provider.protocol;
|
|
35
35
|
const protocolVersion = provider.protocolVersion;
|
|
36
36
|
const rewardSignal = provider.rewardSignal;
|
|
37
|
+
const presentationAudio = provider.presentationAudio;
|
|
37
38
|
const capacity = input.maxRememberedInvocations ?? 1024;
|
|
38
39
|
if (!Number.isSafeInteger(capacity) || capacity < 1 || capacity > 10000) {
|
|
39
40
|
throw new RangeError('maxRememberedInvocations must be from 1 to 10000.');
|
|
@@ -372,7 +373,7 @@ export function createCoordinatedAdProvider(input) {
|
|
|
372
373
|
throw new PresentationExecutionError('disposed');
|
|
373
374
|
}
|
|
374
375
|
try {
|
|
375
|
-
flight.lease = input.presentation.acquire({ kind: request.format, invocationId: request.invocationId });
|
|
376
|
+
flight.lease = input.presentation.acquire({ kind: request.format, invocationId: request.invocationId, audioStart: presentationAudio ?? 'requested' });
|
|
376
377
|
}
|
|
377
378
|
catch (error) {
|
|
378
379
|
if (error instanceof PresentationExecutionError && error.code === 'busy') {
|
|
@@ -419,6 +420,7 @@ export function createCoordinatedAdProvider(input) {
|
|
|
419
420
|
}
|
|
420
421
|
return Object.freeze({
|
|
421
422
|
id: id, protocol: protocol, protocolVersion: protocolVersion, rewardSignal: rewardSignal,
|
|
423
|
+
...(presentationAudio === undefined ? {} : { presentationAudio }),
|
|
422
424
|
getAvailability,
|
|
423
425
|
async preload(supplied) {
|
|
424
426
|
const request = placement(supplied);
|
|
@@ -473,7 +475,8 @@ export function createCoordinatedPlatformGateway(input) {
|
|
|
473
475
|
}
|
|
474
476
|
maybeDetach();
|
|
475
477
|
}
|
|
476
|
-
const
|
|
478
|
+
const purchasePresentation = input.purchasePresentation ?? input.gateway.commerce.presentation;
|
|
479
|
+
const unsubscribe = purchasePresentation?.subscribe((event) => {
|
|
477
480
|
observe(() => {
|
|
478
481
|
const flight = purchases.get(event.idempotencyKey);
|
|
479
482
|
if (flight === undefined || !flight.called || flight.closed) {
|
package/dist/game/index.d.ts
CHANGED
|
@@ -15,12 +15,21 @@ export interface GameServicePorts {
|
|
|
15
15
|
reconcile(): Promise<unknown>;
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
/** One scene's logical play state; execution blocks remain owned by the game runtime. */
|
|
19
|
+
export interface GameGameplayScope {
|
|
20
|
+
setActive(active: boolean): void;
|
|
21
|
+
dispose(): void;
|
|
22
|
+
}
|
|
18
23
|
export interface GamePlatformRuntime<T extends PlatformGateway = PlatformGateway, S extends GameServicePorts = GameServicePorts> {
|
|
19
24
|
readonly gateway: T;
|
|
20
25
|
readonly execution: GameExecutionController;
|
|
21
26
|
readonly presentation: FullScreenPresentationScope;
|
|
22
27
|
readonly services: S;
|
|
23
28
|
readonly actions?: GameActionCoordinator;
|
|
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>;
|
|
24
33
|
/** Reconcile existing journal entries only; this method never opens platform UI. */
|
|
25
34
|
reconcile(): Promise<void>;
|
|
26
35
|
/** Game teardown; native observers and application-owned late recovery may continue. */
|
|
@@ -38,4 +47,6 @@ export declare function createGamePlatformRuntime<T extends PlatformGateway, S e
|
|
|
38
47
|
subscribe(listener: (event: PurchasePresentationEvent) => void): () => void;
|
|
39
48
|
};
|
|
40
49
|
readonly onObserverError?: ObserverErrorHandler;
|
|
50
|
+
/** Must complete synchronously; asynchronous uploads belong at earlier checkpoints. */
|
|
51
|
+
readonly onExit?: () => void;
|
|
41
52
|
}): GamePlatformRuntime<T, S>;
|
package/dist/game/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createGameExecutionController } from '../index.js';
|
|
1
|
+
import { createGameExecutionController, } from '../index.js';
|
|
2
2
|
import { bindGameLifecycle } from '../platform/index.js';
|
|
3
3
|
import { createFullScreenPresentationScope, } from '../presentation/index.js';
|
|
4
4
|
import { createAdClaimEvidenceRecoveryObserver, createCoordinatedPlatformGateway, } from '../ads/index.js';
|
|
@@ -39,12 +39,123 @@ 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
|
+
};
|
|
83
|
+
let hostAudio;
|
|
84
|
+
let unsubscribeHostAudio;
|
|
85
|
+
const gameplayScopes = new Map();
|
|
86
|
+
const lifecycleReason = 'game-runtime:lifecycle';
|
|
87
|
+
let gameplayActive = false;
|
|
88
|
+
let hasPlayed = false;
|
|
89
|
+
let loading = true;
|
|
90
|
+
let unsubscribeActivity;
|
|
91
|
+
const updateActivity = () => {
|
|
92
|
+
const snapshot = execution.getSnapshot();
|
|
93
|
+
const activity = gateway.gameActivity;
|
|
94
|
+
const playable = [...gameplayScopes.values()].some(Boolean);
|
|
95
|
+
const blocked = snapshot.blocks.some((block) => block.channels.includes('simulation')
|
|
96
|
+
&& !(activity?.handlesFocusChanges === true && hasPlayed && block.reason === lifecycleReason));
|
|
97
|
+
const next = !disposed && snapshot.status === 'active' && playable && !blocked;
|
|
98
|
+
if (next && loading) {
|
|
99
|
+
observe(() => {
|
|
100
|
+
activity?.setLoading(false);
|
|
101
|
+
loading = false;
|
|
102
|
+
}, input.onObserverError);
|
|
103
|
+
if (loading) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (next !== gameplayActive) {
|
|
108
|
+
observe(() => {
|
|
109
|
+
activity?.setGameplayActive(next);
|
|
110
|
+
gameplayActive = next;
|
|
111
|
+
if (next) {
|
|
112
|
+
hasPlayed = true;
|
|
113
|
+
}
|
|
114
|
+
}, input.onObserverError);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const createGameplayScope = () => {
|
|
118
|
+
const owner = {};
|
|
119
|
+
let ended = disposed;
|
|
120
|
+
if (!ended) {
|
|
121
|
+
gameplayScopes.set(owner, false);
|
|
122
|
+
}
|
|
123
|
+
return Object.freeze({
|
|
124
|
+
setActive(active) {
|
|
125
|
+
if (!ended && !disposed) {
|
|
126
|
+
gameplayScopes.set(owner, active);
|
|
127
|
+
updateActivity();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
dispose() {
|
|
131
|
+
if (!ended) {
|
|
132
|
+
ended = true;
|
|
133
|
+
gameplayScopes.delete(owner);
|
|
134
|
+
updateActivity();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
};
|
|
42
139
|
const dispose = () => {
|
|
43
140
|
if (disposed) {
|
|
44
141
|
return;
|
|
45
142
|
}
|
|
46
143
|
disposed = true;
|
|
144
|
+
updateActivity();
|
|
145
|
+
if (loading) {
|
|
146
|
+
observe(() => {
|
|
147
|
+
gateway.gameActivity?.setLoading(false);
|
|
148
|
+
loading = false;
|
|
149
|
+
}, input.onObserverError);
|
|
150
|
+
}
|
|
151
|
+
unsubscribeActivity?.();
|
|
152
|
+
unsubscribeExit?.();
|
|
153
|
+
gameplayScopes.clear();
|
|
47
154
|
execution.destroy();
|
|
155
|
+
if (unsubscribeHostAudio !== undefined) {
|
|
156
|
+
observe(unsubscribeHostAudio, input.onObserverError);
|
|
157
|
+
}
|
|
158
|
+
hostAudio?.release();
|
|
48
159
|
actions?.dispose();
|
|
49
160
|
coordinated?.dispose();
|
|
50
161
|
presentation.dispose();
|
|
@@ -83,14 +194,46 @@ export function createGamePlatformRuntime(input) {
|
|
|
83
194
|
});
|
|
84
195
|
}
|
|
85
196
|
lifecycle = bindGameLifecycle({ controller: execution, source: gateway.lifecycle, initialState: input.initialLifecycleState,
|
|
197
|
+
reason: lifecycleReason,
|
|
86
198
|
...(input.onObserverError === undefined ? {} : { onError: input.onObserverError }),
|
|
87
199
|
});
|
|
88
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
|
+
}
|
|
209
|
+
const settings = gateway.gameSettings;
|
|
210
|
+
if (settings !== undefined) {
|
|
211
|
+
const updateMute = (muted) => {
|
|
212
|
+
if (disposed) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (muted && hostAudio === undefined) {
|
|
216
|
+
hostAudio = execution.acquireBlock({ reason: 'game-runtime:host-audio', channels: ['audio'] });
|
|
217
|
+
}
|
|
218
|
+
else if (!muted && hostAudio !== undefined) {
|
|
219
|
+
const owned = hostAudio;
|
|
220
|
+
hostAudio = undefined;
|
|
221
|
+
owned.release();
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
unsubscribeHostAudio = settings.onAudioMuteChange(updateMute);
|
|
225
|
+
updateMute(settings.getAudioMuted());
|
|
226
|
+
}
|
|
227
|
+
observe(() => gateway.gameActivity?.setLoading(true), input.onObserverError);
|
|
228
|
+
unsubscribeActivity = execution.subscribe(updateActivity);
|
|
89
229
|
return Object.freeze({
|
|
90
230
|
gateway,
|
|
91
231
|
execution,
|
|
92
232
|
presentation,
|
|
93
233
|
services,
|
|
234
|
+
createGameplayScope,
|
|
235
|
+
setLoadingProgress,
|
|
236
|
+
completeLoading,
|
|
94
237
|
...(actions === undefined ? {} : { actions }),
|
|
95
238
|
reconcile,
|
|
96
239
|
dispose,
|
package/dist/phaser/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type Phaser from 'phaser';
|
|
2
|
+
import type { GameGameplayScope } from '../game/index.js';
|
|
2
3
|
import type { GameExecutionController, GameExecutionSnapshot } from '../index.js';
|
|
3
4
|
export interface GameplayAudioSink {
|
|
4
5
|
getMuted(): boolean;
|
|
@@ -21,6 +22,7 @@ export interface BindPhaserGameSceneInput {
|
|
|
21
22
|
readonly uiScope?: {
|
|
22
23
|
dispose(): void;
|
|
23
24
|
};
|
|
25
|
+
readonly gameplayScope?: GameGameplayScope;
|
|
24
26
|
/** Phaser pause also suspends input: simulation-only blocking cannot preserve active input. */
|
|
25
27
|
readonly onUnsupportedState: (snapshot: GameExecutionSnapshot, reason: UnsupportedPhaserExecutionState) => void | Promise<void>;
|
|
26
28
|
readonly onError?: (error: unknown) => void | Promise<void>;
|
package/dist/phaser/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export function bindPhaserGameScene(input) {
|
|
|
23
23
|
let ownedVisibility = false;
|
|
24
24
|
let ownedAudio = false;
|
|
25
25
|
let inputBlocked = false;
|
|
26
|
+
let externallyInactive = scene.sys.isPaused();
|
|
26
27
|
const ownedInput = new Set();
|
|
27
28
|
let unsubscribe = () => { };
|
|
28
29
|
function report(error) {
|
|
@@ -99,6 +100,7 @@ export function bindPhaserGameScene(input) {
|
|
|
99
100
|
}
|
|
100
101
|
disposed = true;
|
|
101
102
|
attempt(unsubscribe);
|
|
103
|
+
attempt(() => input.gameplayScope?.dispose());
|
|
102
104
|
// Keep shutdown observation installed while restoration calls into consumer/engine code.
|
|
103
105
|
if (mode !== 'terminal') {
|
|
104
106
|
attempt(() => {
|
|
@@ -141,11 +143,14 @@ export function bindPhaserGameScene(input) {
|
|
|
141
143
|
function onPause() {
|
|
142
144
|
if (!disposed && !applying) {
|
|
143
145
|
ownedPause = false;
|
|
146
|
+
externallyInactive = true;
|
|
147
|
+
attempt(() => input.gameplayScope?.setActive(false));
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
function onResume() {
|
|
147
151
|
if (!disposed && !applying) {
|
|
148
152
|
ownedPause = false;
|
|
153
|
+
externallyInactive = false;
|
|
149
154
|
apply();
|
|
150
155
|
}
|
|
151
156
|
}
|
|
@@ -153,8 +158,11 @@ export function bindPhaserGameScene(input) {
|
|
|
153
158
|
// External sleep owns activity/visibility until an external wake. Never wake it ourselves.
|
|
154
159
|
ownedPause = false;
|
|
155
160
|
ownedVisibility = false;
|
|
161
|
+
externallyInactive = true;
|
|
162
|
+
attempt(() => input.gameplayScope?.setActive(false));
|
|
156
163
|
}
|
|
157
164
|
function onWake() {
|
|
165
|
+
externallyInactive = false;
|
|
158
166
|
if (controller.getSnapshot().blocked['gameplay-input']) {
|
|
159
167
|
inputBlocked = false;
|
|
160
168
|
}
|
|
@@ -175,6 +183,7 @@ export function bindPhaserGameScene(input) {
|
|
|
175
183
|
dispose('terminal');
|
|
176
184
|
break;
|
|
177
185
|
}
|
|
186
|
+
attempt(() => input.gameplayScope?.setActive(!externallyInactive && (scene.sys.isActive() || scene.sys.isPaused())));
|
|
178
187
|
const blocked = snapshot.blocked;
|
|
179
188
|
const simulationUnsupported = blocked.simulation && !blocked['gameplay-input'];
|
|
180
189
|
const audioUnsupported = blocked.audio && audio === undefined && input.audioOwner !== 'game';
|
|
@@ -23,6 +23,7 @@ export interface FullScreenPresentationScope {
|
|
|
23
23
|
acquire(input: {
|
|
24
24
|
readonly kind: FullScreenPresentationKind;
|
|
25
25
|
readonly invocationId: string;
|
|
26
|
+
readonly audioStart?: 'requested' | 'started';
|
|
26
27
|
}): FullScreenPresentationLease;
|
|
27
28
|
getSnapshot(): FullScreenPresentationSnapshot;
|
|
28
29
|
subscribe(listener: UiListener<FullScreenPresentationSnapshot>): () => void;
|
|
@@ -49,8 +49,10 @@ export function createFullScreenPresentationScope(input) {
|
|
|
49
49
|
}
|
|
50
50
|
const kind = request.kind;
|
|
51
51
|
const invocationId = request.invocationId;
|
|
52
|
+
const audioStart = request.audioStart ?? 'requested';
|
|
52
53
|
if (!['purchase', 'rewarded', 'interstitial'].includes(kind)
|
|
53
|
-
|| typeof invocationId !== 'string' || invocationId.trim() === '' || invocationId.length > 512
|
|
54
|
+
|| typeof invocationId !== 'string' || invocationId.trim() === '' || invocationId.length > 512
|
|
55
|
+
|| (audioStart !== 'requested' && audioStart !== 'started')) {
|
|
54
56
|
throw new TypeError('Invalid presentation identity.');
|
|
55
57
|
}
|
|
56
58
|
if (isDisposed()) {
|
|
@@ -66,7 +68,7 @@ export function createFullScreenPresentationScope(input) {
|
|
|
66
68
|
owner = owned;
|
|
67
69
|
try {
|
|
68
70
|
owned.block = input.execution.acquireBlock({
|
|
69
|
-
reason: `presentation:${kind}`, channels: ['simulation', 'gameplay-input', 'audio'],
|
|
71
|
+
reason: `presentation:${kind}`, channels: audioStart === 'started' ? ['simulation', 'gameplay-input'] : ['simulation', 'gameplay-input', 'audio'],
|
|
70
72
|
});
|
|
71
73
|
}
|
|
72
74
|
catch (error) {
|
|
@@ -75,16 +77,46 @@ export function createFullScreenPresentationScope(input) {
|
|
|
75
77
|
}
|
|
76
78
|
throw error;
|
|
77
79
|
}
|
|
80
|
+
let acquiringAudio = false;
|
|
81
|
+
const ensureAudio = () => {
|
|
82
|
+
if (audioStart !== 'started' || acquiringAudio || owned.audioBlock !== undefined || input.execution.getSnapshot().status === 'destroyed') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
acquiringAudio = true;
|
|
86
|
+
try {
|
|
87
|
+
const block = input.execution.acquireBlock({ reason: `presentation:${kind}:audio`, channels: ['audio'] });
|
|
88
|
+
if (owner === owned) {
|
|
89
|
+
owned.audioBlock = block;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
block.release();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
acquiringAudio = false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
78
99
|
publish();
|
|
79
100
|
return Object.freeze({
|
|
80
101
|
markStarted() {
|
|
81
102
|
if (owner === owned && owned.info.state !== 'open') {
|
|
103
|
+
const previousInfo = owned.info;
|
|
104
|
+
ensureAudio();
|
|
105
|
+
if (owner !== owned || owned.info !== previousInfo) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
82
108
|
owned.info = Object.freeze({ ...owned.info, state: 'open' });
|
|
83
109
|
publish();
|
|
84
110
|
}
|
|
85
111
|
},
|
|
86
112
|
markUnknown() {
|
|
87
113
|
if (owner === owned && owned.info.state !== 'unknown') {
|
|
114
|
+
const previousInfo = owned.info;
|
|
115
|
+
// Lost native observations quarantine audio as well as execution.
|
|
116
|
+
ensureAudio();
|
|
117
|
+
if (owner !== owned || owned.info !== previousInfo) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
88
120
|
owned.info = Object.freeze({ ...owned.info, state: 'unknown' });
|
|
89
121
|
publish();
|
|
90
122
|
}
|
|
@@ -95,6 +127,7 @@ export function createFullScreenPresentationScope(input) {
|
|
|
95
127
|
}
|
|
96
128
|
owner = undefined;
|
|
97
129
|
publish();
|
|
130
|
+
owned.audioBlock?.release();
|
|
98
131
|
owned.block?.release();
|
|
99
132
|
},
|
|
100
133
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/game-runtime",
|
|
3
|
-
"version": "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.
|
|
68
|
-
"@mpgd/platform": "0.
|
|
67
|
+
"@mpgd/game-services": "0.19.0",
|
|
68
|
+
"@mpgd/platform": "0.17.0"
|
|
69
69
|
},
|
|
70
70
|
"repository": {
|
|
71
71
|
"type": "git",
|