@mpgd/game-runtime 0.1.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/docs/phaser.md ADDED
@@ -0,0 +1,106 @@
1
+ # Phaser scene binding
2
+
3
+ `@mpgd/game-runtime/phaser` applies headless execution requests to one explicitly
4
+ selected gameplay scene. Keep pause/resume controls in a separate UI scene.
5
+ The adapter never pauses `Phaser.Game`, installs browser lifecycle listeners,
6
+ replaces scene methods, or grants purchases/rewards.
7
+
8
+ ```ts
9
+ import { bindPhaserGameScene } from '@mpgd/game-runtime/phaser';
10
+
11
+ // Inside gameplayScene.create(); the binding also applies at the CREATE event,
12
+ // after SceneManager has finished initializing the scene's running status.
13
+ const binding = bindPhaserGameScene({
14
+ controller: applicationRuntime,
15
+ scene: gameplayScene,
16
+ renderingPolicy: 'visibility',
17
+ resetInput: () => { joystick.cancel(); pressedDirections.clear(); },
18
+ audio: gameplayAudioSink,
19
+ uiScope: gameplayViewScope,
20
+ onUnsupportedState: (snapshot, reason) => showIntegrationError(reason),
21
+ onError: reportError,
22
+ });
23
+ ```
24
+
25
+ | Runtime request | Phaser mapping |
26
+ | --- | --- |
27
+ | Simulation + gameplay input blocked | `sys.pause()` on the selected running scene; render remains available |
28
+ | Input blocked, simulation allowed | Disable only this scene's pointer, keyboard and gamepad plugins; reset injected input state |
29
+ | Rendering blocked | `sys.setVisible(false)`, keeping simulation policy independent; no sleep/wake mapping |
30
+ | Audio blocked with a sink | Mute that explicit gameplay sink; no user volume changes or global sound manager calls |
31
+ | Simulation blocked, input allowed | Unsupported: required observer receives `simulation-requires-input-block`; previous engine controls remain intact |
32
+ | Audio blocked without a sink | Required observer receives `audio-sink-missing`; supported scene channels still apply |
33
+
34
+ Phaser's paused scenes cannot accept input even when the plugin `enabled` flag
35
+ is true. The core retains independent channels, but this binding cannot preserve
36
+ input during simulation pause. The required unsupported-state observer makes
37
+ this engine limitation explicit; it receives the requested snapshot and a safe
38
+ reason code. Each condition is reported once until it clears. Observation errors/rejected promises go to optional `onError`
39
+ without interrupting other cleanup. Its own failures are consumed.
40
+
41
+ `resetInput` must synchronously clear game-owned pressed keys, touch/joystick
42
+ ownership, and held actions. The binding does not guess a game's input model.
43
+ It runs when input blocking starts (and when a blocked scene wakes), preventing
44
+ missed key-up/touch-end events from replaying stale input after resume.
45
+
46
+ The sink exposes `getMuted()` and `setMuted(boolean)` for gameplay audio only.
47
+ A previously muted sink stays muted. The binding restores only mute changes it
48
+ owns; it never starts sounds, restores volume settings, fades, or resumes all audio.
49
+
50
+ ## Ownership and lifetime
51
+
52
+ Use one execution binding per gameplay scene lifetime and route that scene's
53
+ pause/input/visibility policy through the controller. This is a single-writer
54
+ contract, not automatic arbitration of unrelated direct scene writes.
55
+
56
+ The binding records only changes it starts. A previously paused scene is never
57
+ resumed by block release or disposal. Sleeping/stopped scenes are not resumed;
58
+ an external wake reapplies remaining blocks. External sleep revokes the binding's
59
+ pause/visibility ownership. Observable external pause/resume events update ownership;
60
+ a resume while blocked is reconciled immediately. Restoration resumes last so
61
+ resume callbacks can restart a scene without old cleanup overwriting its new binding.
62
+ It does not claim to detect an external pause that
63
+ overlaps an already-owned pause without an observable state transition.
64
+
65
+ `shutdown` and `destroy` remove binding listeners and dispose the supplied view
66
+ scope. Ordinary scene shutdown restores owned input/audio without resuming or
67
+ showing the stopped scene. It does not destroy the shared application runtime.
68
+ A restarted scene must create a fresh binding and scope. A CREATE listener
69
+ handles installation inside `scene.create()` before the first gameplay update.
70
+
71
+ Explicit `dispose()` restores owned state where the scene is still eligible,
72
+ then detaches. Runtime destruction detaches and disposes the scope while keeping
73
+ current engine controls intact: it must not generate a resume or unmute request.
74
+ Late releases and repeated cleanup cannot control a new scene lifetime. Resume
75
+ is driven by controller notifications; no paused-scene update polling is needed.
76
+
77
+ ## Validation and distribution
78
+
79
+ The headless fake tests exercise ownership, input flags/reset, channel mapping,
80
+ CREATE handling, shutdown/restart, external sleep/wake, and terminal cleanup.
81
+ `examples/runtime-controls` separately runs real Phaser 4.2.0 in Chromium and
82
+ asserts actual update/render counters and input behavior. Its initial inactive
83
+ scenario verifies the first update is blocked. The audio fixture is a fake sink,
84
+ not a physical-device audio test. Native targets and every Phaser version are
85
+ not covered by that browser result.
86
+
87
+ The implementation was checked against the installed Phaser 4.2.0 source and
88
+ the official [Systems API](https://docs.phaser.io/api-documentation/4.0.0/class/scenes-systems).
89
+ The separate entrypoint keeps Phaser outside headless runtime imports and
90
+ declarations; its optional peer range starts at the tested 4.2.0 version.
91
+
92
+ Install `@mpgd/game-runtime` and `phaser@^4.2.0` to use this binding. It is shipped
93
+ in the same npm tarball as the headless controller. It does not modify
94
+ `phaser-minigame-runtime`'s native compatibility layer or automatically add a
95
+ dependency to generated games.
96
+
97
+ For TypeScript 7 with Phaser 4.2.0, use the starter's `skipLibCheck: true` setting
98
+ for Phaser's existing declaration errors. The headless entrypoints are separately
99
+ tested with `skipLibCheck: false` and no DOM declarations.
100
+
101
+ If the injected audio sink throws while unmuting during disposal/shutdown, scene
102
+ listeners still detach. The handle retains only its failed audio cleanup and a
103
+ subsequent explicit `dispose()` retries it while the runtime is active. Successful
104
+ cleanup stays idempotent; terminal runtime destruction never retries an unmute.
105
+ Complete this explicit cleanup before reusing the same sink for another binding,
106
+ as required by the single-writer ownership contract. No retry timer is installed.
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@mpgd/game-runtime",
3
+ "version": "0.1.0",
4
+ "description": "Gameplay execution, scoped UI and action coordination with optional Phaser scene bindings.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "mpgd",
8
+ "phaser",
9
+ "game-development",
10
+ "runtime",
11
+ "lifecycle"
12
+ ],
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./ui": {
23
+ "types": "./dist/ui/index.d.ts",
24
+ "default": "./dist/ui/index.js"
25
+ },
26
+ "./platform": {
27
+ "types": "./dist/platform/index.d.ts",
28
+ "default": "./dist/platform/index.js"
29
+ },
30
+ "./actions": {
31
+ "types": "./dist/actions/index.d.ts",
32
+ "default": "./dist/actions/index.js"
33
+ },
34
+ "./phaser": {
35
+ "types": "./dist/phaser/index.d.ts",
36
+ "default": "./dist/phaser/index.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "docs"
42
+ ],
43
+ "devDependencies": {
44
+ "ttsc": "0.18.4",
45
+ "typescript": "7.0.2",
46
+ "vitest": "^4.0.0",
47
+ "phaser": "^4.2.0",
48
+ "@types/node": "^24.0.0"
49
+ },
50
+ "dependencies": {
51
+ "@mpgd/game-services": "0.15.1"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/imjlk/mpgd-kit.git",
56
+ "directory": "packages/game-runtime"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/imjlk/mpgd-kit/issues"
60
+ },
61
+ "homepage": "https://github.com/imjlk/mpgd-kit/tree/main/packages/game-runtime#readme",
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "peerDependencies": {
66
+ "phaser": "^4.2.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "phaser": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "scripts": {
74
+ "build": "cd ../.. && node tools/run-ttsx.mjs tools/package/build-packages.ts @mpgd/game-runtime",
75
+ "check": "ttsc --noEmit && ttsc --noEmit -p tsconfig.headless.json",
76
+ "test": "pnpm build && vitest run && node test/dist-import.mjs && node test/phaser-dist-import.mjs && ttsc --noEmit -p test/tsconfig.json && ttsc --noEmit -p test/tsconfig.phaser.json && node test/package-import.mjs",
77
+ "lint": "pnpm check",
78
+ "format": "ttsc format",
79
+ "fix": "ttsc fix"
80
+ }
81
+ }