@energy8platform/shell 0.7.1 → 0.7.2

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/pixi.d.ts CHANGED
@@ -633,7 +633,7 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
633
633
  tokens: ShellTokens;
634
634
  layout: ShellLayoutMode;
635
635
  soundOn: boolean;
636
- readonly engineVersion = "0.7.1";
636
+ readonly engineVersion = "0.7.2";
637
637
  readonly actions: ShellActions;
638
638
  private renderer;
639
639
  private i18n;
@@ -778,7 +778,7 @@ interface I18n {
778
778
  declare function createI18n(opts: I18nOptions): I18n;
779
779
 
780
780
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
781
- declare const PACKAGE_VERSION = "0.7.1";
781
+ declare const PACKAGE_VERSION = "0.7.2";
782
782
 
783
783
  /** A shell: the renderer-agnostic controller plus the surface facade (safeArea/barHeight/setVisible)
784
784
  * an embedding host reads. `createShell`, `createGameShell` and `createPixiShell` all return this. */
package/dist/pixi.esm.js CHANGED
@@ -1692,7 +1692,7 @@ class KeyboardController {
1692
1692
 
1693
1693
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1694
1694
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
1695
- const PACKAGE_VERSION = '0.7.1';
1695
+ const PACKAGE_VERSION = '0.7.2';
1696
1696
 
1697
1697
  /** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
1698
1698
  function resolveConfig(config) {
@@ -2324,7 +2324,22 @@ function prefersReducedMotion() {
2324
2324
  }
2325
2325
  const easeOutCubic = (p) => 1 - Math.pow(1 - p, 3);
2326
2326
 
2327
- /** Tween 0→1 on the Pixi ticker. Returns a canceler. Skips to the end when motion is reduced. */
2327
+ /**
2328
+ * Tween 0→1 on the Pixi ticker. Returns a canceler. Skips to the end when motion is reduced.
2329
+ *
2330
+ * CONTRACT — when motion is reduced (or `duration <= 0`) this tween does NOT animate: it applies
2331
+ * the final value and calls `onComplete` SYNCHRONOUSLY, before returning. Callers depend on that
2332
+ * (`PixiRenderer.destroy` resolves its teardown promise from `onComplete`, and must not wait on a
2333
+ * ticker that may already be stopped), so it must stay synchronous.
2334
+ *
2335
+ * The consequence for callers: `onComplete` is NOT an async boundary. An `onComplete` that starts
2336
+ * another tween is then direct recursion with no unwind — tween → onComplete → tween → … until
2337
+ * `RangeError: Maximum call stack size exceeded`. That shipped once: the CTA pulse loop
2338
+ * (`widgets.ts` startPulse) restarted itself from `onComplete`, so every player running the OS
2339
+ * "reduce motion" setting crashed the instant the buy-bonus panel painted its hovered CTA. Any
2340
+ * looping animation must therefore check `prefersReducedMotion()` and not loop — which is also
2341
+ * what reduced motion is asking for.
2342
+ */
2328
2343
  function tween(ticker, opts) {
2329
2344
  const ease = opts.ease ?? easeOutCubic;
2330
2345
  if (prefersReducedMotion() || opts.duration <= 0) {
@@ -3296,6 +3311,15 @@ class BuyBonusBadge extends Container {
3296
3311
  startPulse() {
3297
3312
  if (this.pulseCancel)
3298
3313
  return;
3314
+ // A pulse is exactly the kind of looping decoration "reduce motion" asks us to drop — and
3315
+ // under it `tween` has nothing to animate anyway: it snaps to the end and calls `onComplete`
3316
+ // SYNCHRONOUSLY (see its contract), which this loop would answer by starting another one, and
3317
+ // another, with no unwind — `RangeError: Maximum call stack size exceeded`. That was a live
3318
+ // Stake crash: with the OS "reduce motion" setting on, opening the buy-bonus panel painted its
3319
+ // hovered CTA and killed the game, so the player could never buy. Now the button simply sits
3320
+ // still at scale 1.
3321
+ if (prefersReducedMotion())
3322
+ return;
3299
3323
  // both the label (anchor 0.5) and the icon (pivot centred) scale around their centre
3300
3324
  const loop = () => {
3301
3325
  this.pulseCancel = tween(this.ticker, {