@energy8platform/game-engine 0.34.2 → 0.35.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/audio.cjs.js +114 -59
- package/dist/audio.cjs.js.map +1 -1
- package/dist/audio.d.ts +25 -0
- package/dist/audio.esm.js +114 -59
- package/dist/audio.esm.js.map +1 -1
- package/dist/core.cjs.js +222 -66
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.d.ts +25 -0
- package/dist/core.esm.js +223 -67
- package/dist/core.esm.js.map +1 -1
- package/dist/flow.cjs.js +246 -0
- package/dist/flow.cjs.js.map +1 -1
- package/dist/flow.d.ts +192 -33
- package/dist/flow.esm.js +238 -1
- package/dist/flow.esm.js.map +1 -1
- package/dist/host.cjs.js +343 -82
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.d.ts +82 -2
- package/dist/host.esm.js +344 -83
- package/dist/host.esm.js.map +1 -1
- package/dist/index.cjs.js +222 -66
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +72 -0
- package/dist/index.esm.js +223 -67
- package/dist/index.esm.js.map +1 -1
- package/dist/scene-devtools.cjs.js +529 -115
- package/dist/scene-devtools.cjs.js.map +1 -1
- package/dist/scene-devtools.d.ts +187 -34
- package/dist/scene-devtools.esm.js +529 -115
- package/dist/scene-devtools.esm.js.map +1 -1
- package/dist/scene.cjs.js +704 -46
- package/dist/scene.cjs.js.map +1 -1
- package/dist/scene.d.ts +228 -41
- package/dist/scene.esm.js +698 -47
- package/dist/scene.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/audio/AudioManager.ts +111 -53
- package/src/core/GameApplication.ts +47 -5
- package/src/host/buildConfig.ts +17 -4
- package/src/host/createSlotGame.ts +114 -12
- package/src/host/index.ts +3 -0
- package/src/host/types.ts +58 -0
- package/src/loading/LoadingScene.ts +76 -2
- package/src/loading/index.ts +6 -0
- package/src/types.ts +2 -0
package/src/host/types.ts
CHANGED
|
@@ -34,6 +34,58 @@ export interface StakeIntegration {
|
|
|
34
34
|
adapter: BookAdapter | AdapterModule;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** The live Artube bridge, structurally. Kept minimal so game-engine never has to import
|
|
38
|
+
* `@energy8platform/artube-bridge` — see `ArtubeIntegration.load`. */
|
|
39
|
+
export interface ArtubeBridgeLike {
|
|
40
|
+
/** Resolves once the backend has sent its init. */
|
|
41
|
+
ready(): Promise<void>;
|
|
42
|
+
destroy(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What `ArtubeIntegration.load()` must resolve to. `@energy8platform/artube-bridge`'s own entry
|
|
46
|
+
* satisfies this structurally, so `load: () => import('@energy8platform/artube-bridge')` typechecks
|
|
47
|
+
* with no adapter. The launch CLASSIFIER comes from the same module as the bridge: one import, and
|
|
48
|
+
* no chicken-and-egg where the host would have to load something to decide whether to load. */
|
|
49
|
+
export interface ArtubeModule {
|
|
50
|
+
classifyArtubeLaunch: (url: string) => 'artube' | 'blocked' | 'offline';
|
|
51
|
+
ArtubeBridge: new (options: {
|
|
52
|
+
devMode?: boolean;
|
|
53
|
+
gameId?: string;
|
|
54
|
+
url?: string;
|
|
55
|
+
apiBase?: string;
|
|
56
|
+
demoBalance?: number;
|
|
57
|
+
}) => ArtubeBridgeLike;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Artube host integration. There is no per-game artifact to pass: the game's own BACKEND
|
|
61
|
+
* (`@energy8platform/artube-server`) owns the round shape, so the bridge is a pure protocol
|
|
62
|
+
* translator — `load` is the only required field. `gameId` comes from the model, the launch params
|
|
63
|
+
* from the URL. */
|
|
64
|
+
export interface ArtubeIntegration {
|
|
65
|
+
/** REQUIRED. How the host reaches the bridge:
|
|
66
|
+
* `load: () => import('@energy8platform/artube-bridge')`.
|
|
67
|
+
*
|
|
68
|
+
* Why the GAME supplies this instead of the host importing the package itself: a bare
|
|
69
|
+
* `import('@energy8platform/artube-bridge')` inside this always-shipped `/host` entry is resolved
|
|
70
|
+
* STATICALLY by every bundler, so it would have to resolve in games that never opted into Artube
|
|
71
|
+
* and therefore never installed the package — esbuild/webpack fail the build outright, and Vite
|
|
72
|
+
* silently substitutes an empty module (it stubs uninstalled optional peers), which is worse:
|
|
73
|
+
* the game builds and then dies at runtime. With the loader, the specifier only ever appears in
|
|
74
|
+
* the bundle of a game that took the dependency.
|
|
75
|
+
*
|
|
76
|
+
* The loaded chunk is fetched on every launch of such a game (the classifier lives in it), which
|
|
77
|
+
* is the point of the split: only Artube-targeted builds pay for it. */
|
|
78
|
+
load: () => Promise<ArtubeModule>;
|
|
79
|
+
/** Starting virtual balance for a DEMO session (the platform doesn't keep one — the bridge does,
|
|
80
|
+
* client-side). Default: the backend's own configured demo balance. Ignored for real sessions. */
|
|
81
|
+
demoBalance?: number;
|
|
82
|
+
/** Origin of the game's backend. Default (and the only supported PRODUCTION value) is the launch
|
|
83
|
+
* URL's own origin: Artube serves frontend and backend on one domain, split by path (`/api/**`).
|
|
84
|
+
* Override only for local dev against a backend on another port — prefer proxying `/api` from the
|
|
85
|
+
* dev server (what the `BUILD_TARGET=artube` target does) so dev matches production. */
|
|
86
|
+
apiBase?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
37
89
|
/** One scene registered with the host: a key + its constructor. The list order matters — the
|
|
38
90
|
* first scene that is eligible for the current launch mode is the start scene (unless an explicit
|
|
39
91
|
* `startScene` overrides it). */
|
|
@@ -77,6 +129,10 @@ export interface CreateSlotGameOptions<T extends SlotSpinResultBase = SlotSpinRe
|
|
|
77
129
|
textureDefaults?: boolean;
|
|
78
130
|
dev?: boolean;
|
|
79
131
|
stake?: StakeIntegration;
|
|
132
|
+
/** Run on Artube when the launch URL says so (`?sessionId=…`):
|
|
133
|
+
* `artube: { load: () => import('@energy8platform/artube-bridge') }`. See `ArtubeIntegration`
|
|
134
|
+
* for why the game supplies the loader. Build with `BUILD_TARGET=artube`. */
|
|
135
|
+
artube?: ArtubeIntegration;
|
|
80
136
|
shell?: SlotShellOptions;
|
|
81
137
|
/** Customise the bonus bar readout for games whose bonus isn't plain free spins (adventure,
|
|
82
138
|
* hold-and-spin, respins). Omit for the free-spins default. See `BonusReadoutConfig`. */
|
|
@@ -101,5 +157,7 @@ export type ShellFactory = (config: PixiShellConfig) => Shell;
|
|
|
101
157
|
export interface SlotGameHandle {
|
|
102
158
|
game: GameApplication;
|
|
103
159
|
stakeBridge: StakeBridge | null;
|
|
160
|
+
/** The live Artube bridge — non-null only on a real Artube launch (`opts.artube` + `?sessionId=…`). */
|
|
161
|
+
artubeBridge: ArtubeBridgeLike | null;
|
|
104
162
|
shell: Shell | null;
|
|
105
163
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Scene } from '../core/Scene';
|
|
2
2
|
import type { LoadingScreenConfig } from '../types';
|
|
3
3
|
import {
|
|
4
|
+
createCSSPreloader,
|
|
4
5
|
setCSSPreloaderProgress,
|
|
5
6
|
waitCSSPreloaderTap,
|
|
6
7
|
removeCSSPreloader,
|
|
8
|
+
hasExternalOverlay,
|
|
9
|
+
externalOverlayHold,
|
|
10
|
+
releaseExternalOverlay,
|
|
7
11
|
} from '@energy8platform/platform-core/loading';
|
|
8
12
|
|
|
9
13
|
interface LoadingSceneData {
|
|
@@ -21,6 +25,14 @@ interface LoadingSceneData {
|
|
|
21
25
|
* tap-to-start → `waitCSSPreloaderTap`, then fades it out via
|
|
22
26
|
* `removeCSSPreloader` before entering the game. One continuous overlay from
|
|
23
27
|
* boot to gameplay — no second logo, no mid-load flash.
|
|
28
|
+
*
|
|
29
|
+
* When the game supplied its own overlay (`loading.externalOverlay`, e.g.
|
|
30
|
+
* Artube's `LoaderViewController`), this scene is also the HAND-OVER point: that
|
|
31
|
+
* overlay covered the gap this scene's existence ends — the bundle download,
|
|
32
|
+
* Pixi init and the SDK handshake, none of which the engine can paint over. The
|
|
33
|
+
* first thing `onEnter` does is mount the preloader, wait for it to be painted,
|
|
34
|
+
* and dismiss the game's overlay. Everything after that line is identical on
|
|
35
|
+
* every platform.
|
|
24
36
|
*/
|
|
25
37
|
export class LoadingScene extends Scene {
|
|
26
38
|
private _engine!: any;
|
|
@@ -40,6 +52,12 @@ export class LoadingScene extends Scene {
|
|
|
40
52
|
this._targetScene = targetScene;
|
|
41
53
|
this._targetData = targetData;
|
|
42
54
|
this._config = engine.config.loading ?? {};
|
|
55
|
+
|
|
56
|
+
// Take the screen from a game-supplied loading overlay, if there is one. Before any awaited
|
|
57
|
+
// work: from here on the player is looking at OUR loading screen, and `_startTime` (which
|
|
58
|
+
// `minDisplayTime` is measured from) must start when that becomes true.
|
|
59
|
+
await this.takeOverFromExternalOverlay();
|
|
60
|
+
|
|
43
61
|
this._startTime = Date.now();
|
|
44
62
|
|
|
45
63
|
// Initialize asset manager
|
|
@@ -104,8 +122,10 @@ export class LoadingScene extends Scene {
|
|
|
104
122
|
this._displayedProgress = 1;
|
|
105
123
|
this.updateLoaderBar(1);
|
|
106
124
|
|
|
107
|
-
// Wait for the player's tap — resolves immediately when tapToStart is
|
|
108
|
-
//
|
|
125
|
+
// Wait for the player's tap — resolves immediately when tapToStart is false — then enter the
|
|
126
|
+
// game. This is the preloader's gate and it reads the preloader's config, so it means the same
|
|
127
|
+
// thing on every target: a game-supplied overlay has no say in it, and by now no part in the
|
|
128
|
+
// screen either. It was dismissed at the hand-over above; the player is looking at ours.
|
|
109
129
|
await waitCSSPreloaderTap();
|
|
110
130
|
await this.transitionToGame();
|
|
111
131
|
}
|
|
@@ -131,6 +151,60 @@ export class LoadingScene extends Scene {
|
|
|
131
151
|
void removeCSSPreloader(this.hostElement());
|
|
132
152
|
}
|
|
133
153
|
|
|
154
|
+
// ─── Hand-over from a game-supplied overlay ────────────
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Swap a game-supplied loading overlay for the engine's own loading screen.
|
|
158
|
+
*
|
|
159
|
+
* The overlay (Artube's) has been on screen since before this bundle was fetched, covering a gap
|
|
160
|
+
* nothing of ours could. Its job ends here, at the first frame the engine paints; the player then
|
|
161
|
+
* gets the game's own brand, progress bar and tap-to-start, exactly as on every other target.
|
|
162
|
+
*
|
|
163
|
+
* The order of the four steps is the whole design, and each is wrong on its own:
|
|
164
|
+
*
|
|
165
|
+
* 0. Wait out whatever the overlay is still owed on screen (`externalOverlayMinDisplayTime`,
|
|
166
|
+
* default 1.5s, plus room for a phase crossfade already in flight). The gap this overlay
|
|
167
|
+
* covers can be under half a second, which is not long enough for a partner's brand to
|
|
168
|
+
* register. Waiting here — BEFORE mounting ours — rather than after is what keeps the two
|
|
169
|
+
* screens' timelines from overlapping: our splash and brand floor start when the player can
|
|
170
|
+
* actually see them, not behind someone else's overlay. On any boot slower than the floor
|
|
171
|
+
* this step costs nothing, and on a non-Artube target it is not reached at all.
|
|
172
|
+
* 1. Mount the preloader, opaque and full-bleed, while theirs is still up. Both are on screen
|
|
173
|
+
* together for a few frames, so there is never a moment with neither, whatever happens next.
|
|
174
|
+
* 2. Wait for that frame to actually be PAINTED — mounting only queues it. Dismissing theirs
|
|
175
|
+
* before the paint is precisely the flash of bare background this ordering exists to avoid.
|
|
176
|
+
* Two `requestAnimationFrame`s: the first callback runs before the frame it belongs to is
|
|
177
|
+
* composited, the second after. Two frames is also enough for Pixi's own rAF-driven ticker
|
|
178
|
+
* to have rendered this scene at least once, so "the loading scene has painted" is literally
|
|
179
|
+
* true by the time step 3 runs.
|
|
180
|
+
* 3. Only then dismiss theirs. Their `hideLoader()` plays a 0.3s fade and removes the element.
|
|
181
|
+
* Not waiting for that fade is deliberate — it is an animation on someone else's element,
|
|
182
|
+
* and blocking a boot on it would be a hang waiting to happen.
|
|
183
|
+
*
|
|
184
|
+
* Which of the two is visually on top is the host page's business, not ours, and it does NOT
|
|
185
|
+
* change the guarantee. On a typical game page (`#game { position: fixed; inset: 0 }`) the fixed
|
|
186
|
+
* container establishes a stacking context, so the preloader's z-index is scoped inside it and
|
|
187
|
+
* Artube's `position: fixed; z-index: 9999` sits above — their fade then crossfades onto our
|
|
188
|
+
* loading screen, which is what was observed live and looks right. On a page where ours wins
|
|
189
|
+
* instead, their fade simply plays underneath, unseen. Either way the seam is covered, because
|
|
190
|
+
* what step 2 buys is that OUR screen is already painted before theirs starts going away.
|
|
191
|
+
*/
|
|
192
|
+
private async takeOverFromExternalOverlay(): Promise<void> {
|
|
193
|
+
if (!hasExternalOverlay()) return;
|
|
194
|
+
await externalOverlayHold();
|
|
195
|
+
createCSSPreloader(this.hostElement(), this._config);
|
|
196
|
+
await this.nextPaint();
|
|
197
|
+
releaseExternalOverlay();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Resolves after the browser has composited at least one frame (see step 2 above). */
|
|
201
|
+
private nextPaint(): Promise<void> {
|
|
202
|
+
if (typeof requestAnimationFrame !== 'function') return Promise.resolve();
|
|
203
|
+
return new Promise<void>((resolve) => {
|
|
204
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
134
208
|
// ─── Progress ──────────────────────────────────────────
|
|
135
209
|
|
|
136
210
|
private updateLoaderBar(progress: number): void {
|
package/src/loading/index.ts
CHANGED
|
@@ -7,6 +7,12 @@ export {
|
|
|
7
7
|
setCSSPreloaderProgress,
|
|
8
8
|
waitCSSPreloaderTap,
|
|
9
9
|
removeCSSPreloader,
|
|
10
|
+
adoptExternalOverlay,
|
|
11
|
+
advanceExternalOverlay,
|
|
12
|
+
externalOverlayHold,
|
|
13
|
+
releaseExternalOverlay,
|
|
14
|
+
hasExternalOverlay,
|
|
15
|
+
DEFAULT_EXTERNAL_MIN_DISPLAY_MS,
|
|
10
16
|
buildLogoSVG,
|
|
11
17
|
LOADER_BAR_MAX_WIDTH,
|
|
12
18
|
} from '@energy8platform/platform-core/loading';
|
package/src/types.ts
CHANGED
|
@@ -37,6 +37,7 @@ export enum Orientation {
|
|
|
37
37
|
// existing game-engine consumers keep their imports.
|
|
38
38
|
import type {
|
|
39
39
|
LoadingScreenConfig,
|
|
40
|
+
ExternalLoadingOverlay,
|
|
40
41
|
AssetManifest,
|
|
41
42
|
AssetBundle,
|
|
42
43
|
AssetEntry,
|
|
@@ -44,6 +45,7 @@ import type {
|
|
|
44
45
|
|
|
45
46
|
export type {
|
|
46
47
|
LoadingScreenConfig,
|
|
48
|
+
ExternalLoadingOverlay,
|
|
47
49
|
AssetManifest,
|
|
48
50
|
AssetBundle,
|
|
49
51
|
AssetEntry,
|