@energy8platform/game-engine 0.33.6 → 0.33.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@energy8platform/game-engine",
3
- "version": "0.33.6",
3
+ "version": "0.33.8",
4
4
  "description": "Universal casino game engine built on PixiJS v8 and @energy8platform/game-sdk",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs.js",
@@ -4,21 +4,24 @@
4
4
  * The wallet emits balance changes at two moments: the DEBIT lands when `play()` is called (before
5
5
  * the scene animates), and the CREDIT (win) lands when the round settles — `/wallet/end-round`
6
6
  * fires asynchronously AFTER the final segment's ack, i.e. after `present()`. The HUD-timing rule is
7
- * "balance updates only after present()", so:
8
- * - while a segment is between `beginPlay()` and `afterPresent()`, balance changes are BUFFERED
9
- * (the debit must not flash before the animation),
10
- * - `afterPresent()` paints the latest buffered value (the debit),
11
- * - any later change (the async win credit) paints immediately, because the gate is open again.
7
+ * "the stake leaves your balance the instant you spin; the win is only added after the animation":
8
+ * - a DEBIT (balance goes DOWN) paints IMMEDIATELY, even between `beginPlay()` and
9
+ * `afterPresent()` — the player must see the stake deducted the moment they press spin,
10
+ * - a CREDIT (balance goes UP) that lands DURING that window is BUFFERED — the win must not
11
+ * update the balance before the animation plays out,
12
+ * - `afterPresent()` flushes any buffered credit,
13
+ * - a credit that lands after the window (the usual case — end-round settles after the final ack)
14
+ * paints immediately, because the gate is open again.
12
15
  *
13
16
  * `balance` always reflects the true latest wallet value (regardless of gating) so an affordability
14
17
  * guard can read it. Pure + unit-testable: the shell paint is injected.
15
18
  */
16
19
  export interface BalanceGate {
17
- /** Record a wallet balance change. Paints it now unless a present is in flight. */
20
+ /** Record a wallet balance change. Debits paint now; a credit mid-present waits for afterPresent. */
18
21
  onBalance(amount: number): void;
19
- /** A play() was issued — suppress painting until the matching afterPresent() (debit must wait). */
22
+ /** A play() was issued — buffer credits until the matching afterPresent() (debits still paint). */
20
23
  beginPlay(): void;
21
- /** A segment finished animating — paint the latest balance and re-open the gate. */
24
+ /** A segment finished animating — flush any buffered credit and re-open the gate. */
22
25
  afterPresent(): void;
23
26
  /** The latest wallet balance, painted or not (for affordability checks). */
24
27
  readonly balance: number;
@@ -26,17 +29,25 @@ export interface BalanceGate {
26
29
 
27
30
  export function createBalanceGate(paint: (amount: number) => void, initial = 0): BalanceGate {
28
31
  let latest = initial;
32
+ let painted = initial;
29
33
  let suppressed = false;
34
+ const show = (amount: number): void => {
35
+ painted = amount;
36
+ paint(amount);
37
+ };
30
38
  return {
31
39
  onBalance(amount: number): void {
32
40
  latest = amount;
33
- if (!suppressed) paint(amount);
41
+ // During play→present, hold a CREDIT (balance rising) back so the win doesn't post before the
42
+ // animation. A DEBIT (balance falling — the stake) always paints now: spin deducts instantly.
43
+ if (suppressed && amount > painted) return;
44
+ show(amount);
34
45
  },
35
46
  beginPlay(): void {
36
47
  suppressed = true;
37
48
  },
38
49
  afterPresent(): void {
39
- paint(latest);
50
+ if (latest !== painted) show(latest); // flush a credit that landed mid-present
40
51
  suppressed = false;
41
52
  },
42
53
  get balance(): number {
@@ -245,10 +245,11 @@ export async function createSlotGame<T extends SlotSpinResultBase = SlotSpinResu
245
245
  // (hidden over the intro / non-slot scenes). Applies in BOTH base and replay modes.
246
246
  shell.setVisible(!!gameScene());
247
247
  game.scenes.on('change', () => shell!.setVisible(!!gameScene()));
248
- // The gate tracks the live wallet (for the affordability guard) but only PAINTS the balance per
249
- // the HUD-timing rule: the debit is buffered during play→present and shown at afterPresent; the
250
- // async win credit (/wallet/end-round, after the final ack) paints when it lands. `balanceGate`
251
- // is the single source for both the displayed balance and `ensureAffordable`.
248
+ // The gate tracks the live wallet (for the affordability guard) and PAINTS the balance per the
249
+ // HUD-timing rule: the debit paints immediately (the stake leaves the balance on spin); a win
250
+ // credit landing during play→present is held to afterPresent so it doesn't post before the
251
+ // animation; the async credit (/wallet/end-round, after the final ack) paints when it lands.
252
+ // `balanceGate` is the single source for both the displayed balance and `ensureAffordable`.
252
253
  const balanceGate = createBalanceGate((b) => shell!.setBalance(b), balance);
253
254
  ps?.on('balanceUpdate', (d: { balance: number }) => {
254
255
  balanceGate.onBalance(d.balance);
@@ -542,7 +543,8 @@ export async function createSlotGame<T extends SlotSpinResultBase = SlotSpinResu
542
543
  // animation has finished — returning void would reopen it instantly, over a running animation.
543
544
  return runRound<T>(
544
545
  {
545
- // Suppress the debit paint from play() until this segment's afterPresent (HUD timing).
546
+ // Open the play→present window: the debit still paints immediately, but a win credit that
547
+ // lands mid-animation is held until afterPresent (HUD timing).
546
548
  play: (a, b, rid) => {
547
549
  balanceGate.beginPlay();
548
550
  return slotPlay.play(a, b, rid);