@energy8platform/game-engine 0.33.8 → 0.33.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/dist/host.d.ts CHANGED
@@ -956,6 +956,43 @@ declare function resolveReplayBonusId(model: GameModel, stakeMode: string): stri
956
956
  */
957
957
  declare function resolveStartScene(scenes: SceneRegistration[], isReplay: boolean, explicitStart?: string): string;
958
958
 
959
+ /**
960
+ * Gate for the PROGRESSIVE (cascade / tumble) WIN readout.
961
+ *
962
+ * By default the WIN readout is entirely host-driven: cleared to 0 when a segment starts, set to
963
+ * that segment's win once `onSpin` resolves. A cascade game pays in steps, so its scene wants the
964
+ * number to climb WHILE the segment presents — `api.shell.reportWin(amountSoFar)`.
965
+ *
966
+ * The gate keeps the host in charge:
967
+ * - reports are honoured ONLY between `open()` (segment start, WIN already cleared) and `close()`
968
+ * (the host paints the segment's final value) — a scene still animating after an abort, or one
969
+ * reporting from a mode transition, can't overwrite the host's number,
970
+ * - `amountSoFar` is ABSOLUTE (the segment's win up to now, not the step delta), so a re-report or
971
+ * a collapse-to-final on skip is idempotent. Reports should be non-decreasing within a segment;
972
+ * a lower value counts the readout back DOWN,
973
+ * - garbage (NaN / Infinity) is dropped and negatives clamp to 0, so a math bug can't paint "NaN"
974
+ * into the bar.
975
+ *
976
+ * Pure + unit-testable: the shell paint is injected.
977
+ */
978
+ interface WinReportOptions {
979
+ /** `false` snaps instead of counting up (e.g. collapsing to the final value on skip). */
980
+ animate?: boolean;
981
+ /** Count-up length in ms (shell default 450) — pass the cascade step's length. */
982
+ durationMs?: number;
983
+ }
984
+ interface WinReporter {
985
+ /** Scene-facing (`api.shell.reportWin`): the win accumulated by the presenting segment so far. */
986
+ report(amountSoFar: number, opts?: WinReportOptions): void;
987
+ /** A segment starts presenting — the scene may now grow WIN. */
988
+ open(): void;
989
+ /** The host takes the readout back (final value, round end, or an error). */
990
+ close(): void;
991
+ /** True while reports are honoured. */
992
+ readonly accepting: boolean;
993
+ }
994
+ declare function createWinReporter(paint: (amount: number, opts?: WinReportOptions) => void): WinReporter;
995
+
959
996
  /** Everything a scene needs to render one segment. The host builds it per segment. */
960
997
  interface RenderContext {
961
998
  /** Bet for this round (major units). Stable for the whole round. */
@@ -1023,6 +1060,25 @@ interface SceneShell {
1023
1060
  bottom: number;
1024
1061
  left: number;
1025
1062
  };
1063
+ /**
1064
+ * Grow the shell's WIN readout WHILE a segment presents — for cascade/tumble games that pay in
1065
+ * steps instead of one lump at the end.
1066
+ *
1067
+ * `amountSoFar` is ABSOLUTE: the win accumulated by this segment up to now (not the step's
1068
+ * delta), so a re-report, a skip, or an aborted step can just restate the truth. The host owns
1069
+ * the readout around it — it clears WIN to 0 when the segment starts and sets the segment's
1070
+ * final value once `onSpin` resolves (counting up from your last report, so matching numbers
1071
+ * produce no jump). In a bonus this moves WIN only; the Total Win accumulator still lands once
1072
+ * per segment.
1073
+ *
1074
+ * Only honoured while a segment is presenting (inside `onSpin`); calls from anywhere else are
1075
+ * ignored, so a scene still ticking after an abort can't overwrite the host's final number.
1076
+ *
1077
+ * `durationMs` sets this count-up's length (default 450ms) — pass your step length (or a shorter
1078
+ * one under turbo) so each count-up finishes before the next step lands. `{ animate: false }`
1079
+ * snaps, e.g. when collapsing to the final value on skip.
1080
+ */
1081
+ reportWin(amountSoFar: number, opts?: WinReportOptions): void;
1026
1082
  }
1027
1083
  interface AutoplaySceneState {
1028
1084
  running: boolean;
@@ -1070,5 +1126,5 @@ interface SlotSceneController<T extends SlotSpinResultBase = SlotSpinResultBase>
1070
1126
  onResume?(): void;
1071
1127
  }
1072
1128
 
1073
- export { buildShellConfig, createSlotGame, resolveReplayBonusId, resolveStartScene, stakeForAction };
1074
- export type { AutoplaySceneState, CreateSlotGameOptions, OverlayShowOptions, RenderContext, SceneApi, SceneAudio, SceneNavData, SceneOverlay, SceneRegistration, SceneShell, ShellFactory, SlotGameHandle, SlotSceneController, SlotShellOptions, StakeIntegration };
1129
+ export { buildShellConfig, createSlotGame, createWinReporter, resolveReplayBonusId, resolveStartScene, stakeForAction };
1130
+ export type { AutoplaySceneState, CreateSlotGameOptions, OverlayShowOptions, RenderContext, SceneApi, SceneAudio, SceneNavData, SceneOverlay, SceneRegistration, SceneShell, ShellFactory, SlotGameHandle, SlotSceneController, SlotShellOptions, StakeIntegration, WinReportOptions, WinReporter };
package/dist/host.esm.js CHANGED
@@ -2501,6 +2501,11 @@ async function createSlotGame(opts) {
2501
2501
  size: () => ({ width: game.app.screen.width, height: game.app.screen.height }),
2502
2502
  });
2503
2503
  game.on('resize', ({ width, height }) => overlayCtl.resize(width, height));
2504
+ // Progressive WIN readout for cascade/tumble scenes. The window is open from the moment a
2505
+ // segment starts (WIN cleared to 0) until the host has painted that segment's final win; outside
2506
+ // it the host owns the readout alone. See winReporter.ts.
2507
+ const { createWinReporter } = await Promise.resolve().then(function () { return winReporter; });
2508
+ const winReporter$1 = createWinReporter((amount, opts) => shell.setWin(amount, opts));
2504
2509
  // Capabilities injected once per controller scene via onCreate (see `gameScene`/`ensureCreated`).
2505
2510
  sceneApi = {
2506
2511
  audio: createSceneAudio(game.audio),
@@ -2509,6 +2514,7 @@ async function createSlotGame(opts) {
2509
2514
  get safeArea() {
2510
2515
  return shell.safeArea;
2511
2516
  },
2517
+ reportWin: winReporter$1.report,
2512
2518
  },
2513
2519
  formatAmount: (v) => shell.formatWin(v),
2514
2520
  get bet() {
@@ -2748,12 +2754,15 @@ async function createSlotGame(opts) {
2748
2754
  // then counts UP to this segment's delta. prevWin (the cumulative-delta tracker) is
2749
2755
  // untouched; this only resets the DISPLAY.
2750
2756
  shell.setWin(0, { animate: false });
2757
+ winReporter$1.open(); // the scene may now grow WIN per cascade step
2751
2758
  },
2752
2759
  onSpinStart: () => scene.onSpinStart?.(),
2753
2760
  onSpinEnd: (last, ctx) => scene.onSpinEnd?.(last, ctx),
2754
2761
  afterPresent: (r) => {
2755
2762
  // WIN readout = THIS spin's win (cumulative delta); the cumulative total goes to the
2756
- // bonus counter (totalWin) via settle(), not the WIN readout.
2763
+ // bonus counter (totalWin) via settle(), not the WIN readout. A scene that reported
2764
+ // per-step wins already landed on this number, so the count-up is a no-op there.
2765
+ winReporter$1.close();
2757
2766
  shell.setWin(r.totalWin - prevWin);
2758
2767
  prevWin = r.totalWin;
2759
2768
  balanceGate.afterPresent();
@@ -2765,6 +2774,7 @@ async function createSlotGame(opts) {
2765
2774
  .catch(showPlayError)
2766
2775
  .finally(() => {
2767
2776
  presenting = false;
2777
+ winReporter$1.close(); // also closes the window when a segment threw mid-flight
2768
2778
  shell.setBusy(false);
2769
2779
  });
2770
2780
  };
@@ -2809,6 +2819,8 @@ async function createSlotGame(opts) {
2809
2819
  shell.setMode(bonusShellMode);
2810
2820
  }
2811
2821
  shell.setWin(0, { animate: false }); // clear WIN before this segment animates (see playRound)
2822
+ if (animate)
2823
+ winReporter$1.open(); // a fast-forward drain doesn't present → no reports expected
2812
2824
  if (animate)
2813
2825
  await scene.onSpin(r, ctx);
2814
2826
  if (inBonus) {
@@ -2816,6 +2828,7 @@ async function createSlotGame(opts) {
2816
2828
  if (v)
2817
2829
  applyBonusReadout(r, v, ctx.mode);
2818
2830
  }
2831
+ winReporter$1.close();
2819
2832
  shell.setWin(r.totalWin - prevWin); // THIS spin's win, not the cumulative bonus total
2820
2833
  prevWin = r.totalWin;
2821
2834
  ps.playAck(raw); // settles via /wallet/end-round on the FINAL segment
@@ -2840,6 +2853,7 @@ async function createSlotGame(opts) {
2840
2853
  }
2841
2854
  }
2842
2855
  finally {
2856
+ winReporter$1.close(); // also closes the window when a drained segment threw
2843
2857
  shell.setBusy(false);
2844
2858
  }
2845
2859
  };
@@ -3369,6 +3383,31 @@ var sceneStart = /*#__PURE__*/Object.freeze({
3369
3383
  resolveStartScene: resolveStartScene
3370
3384
  });
3371
3385
 
3386
+ function createWinReporter(paint) {
3387
+ let accepting = false;
3388
+ return {
3389
+ report(amountSoFar, opts) {
3390
+ if (!accepting || !Number.isFinite(amountSoFar))
3391
+ return;
3392
+ paint(Math.max(0, amountSoFar), opts);
3393
+ },
3394
+ open() {
3395
+ accepting = true;
3396
+ },
3397
+ close() {
3398
+ accepting = false;
3399
+ },
3400
+ get accepting() {
3401
+ return accepting;
3402
+ },
3403
+ };
3404
+ }
3405
+
3406
+ var winReporter = /*#__PURE__*/Object.freeze({
3407
+ __proto__: null,
3408
+ createWinReporter: createWinReporter
3409
+ });
3410
+
3372
3411
  // packages/game-engine/src/host/index.ts
3373
3412
 
3374
3413
  /**
@@ -3776,5 +3815,5 @@ var autoplay = /*#__PURE__*/Object.freeze({
3776
3815
  createAutoplayLoop: createAutoplayLoop
3777
3816
  });
3778
3817
 
3779
- export { buildShellConfig, createSlotGame, resolveReplayBonusId, resolveStartScene, stakeForAction };
3818
+ export { buildShellConfig, createSlotGame, createWinReporter, resolveReplayBonusId, resolveStartScene, stakeForAction };
3780
3819
  //# sourceMappingURL=host.esm.js.map