{"version":3,"file":"index.cjs","names":[],"sources":["../src/symbols/PerspectiveCell.ts","../src/symbols/SpriteSymbol.ts","../src/symbols/AnimatedSpriteSymbol.ts","../src/symbols/EmptySymbol.ts","../src/snapshot/SpinTextureCache.ts","../src/snapshot/StaticSpinSymbol.ts","../src/symbols/CardSymbol.ts","../src/spin/anticipationRecipes.ts","../src/spin/modes/CascadeMode.ts","../src/spin/modes/ImmediateMode.ts","../src/board/BoardGrid.ts","../src/board/HwTypes.ts","../src/board/HoldAndWinState.ts","../src/board/HoldAndWinBoard.ts","../src/board/HoldAndWinBuilder.ts","../src/wins/Win.ts","../src/wins/WinPresenter.ts","../src/utils/gsapTicker.ts"],"sourcesContent":["import { PerspectiveMesh, type Container, type Texture } from 'pixi.js';\nimport type { ReelCellInset, ReelCellQuad } from '../config/types.js';\n\n/**\n * The slice of its cell a texture's opaque art really occupies.\n *\n * TexturePacker trims the transparent margin off each frame, so a 300x300\n * symbol whose art is a 152x152 blob at (74, 74) ships as a 152x152 frame. A\n * `Sprite` puts that back; a mesh does not, so the reel has to be told or it\n * projects - and inflates - the whole cell.\n *\n * `null` for an untrimmed texture, which needs no correction.\n */\nexport function textureCellInset(texture: Texture): ReelCellInset | null {\n  const trim = texture.trim;\n  const orig = texture.orig;\n  if (!trim || orig.width <= 0 || orig.height <= 0) return null;\n  if (trim.width === orig.width && trim.height === orig.height) return null;\n  return {\n    left: trim.x / orig.width,\n    top: trim.y / orig.height,\n    right: (trim.x + trim.width) / orig.width,\n    bottom: (trim.y + trim.height) / orig.height,\n  };\n}\n\n/**\n * Whether a texture can go through the perspective mesh.\n *\n * Only textures owning their whole source qualify. An atlas SUB-frame does\n * not: the mesh addresses its source with plain 0..1 UVs, and remapping them\n * onto the frame - via `texture.uvs`, via `textureMatrix`, or leaving it to\n * PixiJS - has not produced a correct draw here. Unmapped, the cell shows the\n * whole sheet; mapped by hand, a magnified crop of the right frame, which says\n * the mapping is applied somewhere else too. Rather than ship either, a\n * sub-frame falls back to the affine fit in `ReelSymbol.applyCellQuad()` -\n * correct, just not keystoned.\n *\n * Generated textures, single loaded images and render textures pass. Atlas\n * frames - the common production case - do not, yet.\n */\nexport function canProjectTexture(texture: Texture): boolean {\n  const source = texture.source;\n  if (!source) return false;\n  if (texture.rotate !== 0) return false;\n  const { frame, orig } = texture;\n  if (frame.x !== 0 || frame.y !== 0) return false;\n  if (frame.width !== source.width || frame.height !== source.height) return false;\n  return orig.width === frame.width && orig.height === frame.height;\n}\n\n/**\n * Tessellation of the projected quad. PixiJS builds the perspective by\n * interpolating UVs across a grid, so this is the quality knob: too few and a\n * keystoned cell reads as a plain stretch, too many and every symbol on the\n * board costs a pile of vertices. Slot cells are small and their trapezoids are\n * mild, so the PixiJS default of 10 is more than enough.\n */\nconst VERTICES = 10;\n\n/**\n * Draws a texture through a real perspective quad.\n *\n * What makes a curved reel a projection rather than a squash. A `Container`\n * transform is affine - it can scale a cell, never turn it into a trapezoid -\n * so a cell rotated away comes out a smaller rectangle instead of one with a\n * narrower far edge. `PerspectiveMesh` maps the texture onto four arbitrary\n * corners with perspective-correct interpolation, and costs no render pass\n * because the symbol already owns a texture.\n *\n * Lazy: a game that never calls `curve()` allocates no mesh and keeps\n * rendering through its plain sprite.\n *\n * @internal Shared by `SpriteSymbol` and `AnimatedSpriteSymbol`. Use it the\n * same way from a custom symbol whose content is a single texture.\n */\nexport class PerspectiveCell {\n  private _mesh: PerspectiveMesh | null = null;\n  private _active = false;\n  private _uvScratch: Float32Array | null = null;\n  private _uvSource: Texture | null = null;\n\n  /**\n   * @param _view the symbol's own view, which the mesh is parented to\n   * @param _flat the display object used when the reel is flat. hidden while\n   *   the mesh is driving, so the two never draw on top of each other\n   */\n  constructor(\n    private readonly _view: Container,\n    private readonly _flat: Container,\n  ) {}\n\n  /** True while the perspective mesh is the thing being drawn. */\n  get isActive(): boolean {\n    return this._active;\n  }\n\n  /**\n   * The mesh, once one exists. Animate THIS, not the symbol's view, for a win\n   * pulse on a curved reel - it is pivoted on the quad's centre, so a scale\n   * tween pulses in place instead of swinging out from the cell corner.\n   */\n  get mesh(): PerspectiveMesh | null {\n    return this._mesh;\n  }\n\n  /**\n   * Project the cell, or hand rendering back to the flat display object when\n   * `quad` is `null`.\n   *\n   * @param quad the projected footprint, view-local\n   * @param texture what to draw. re-read on every call so an animated symbol\n   *   can swap frames without telling us\n   */\n  apply(quad: ReelCellQuad | null, texture: Texture): boolean {\n    if (quad === null || !canProjectTexture(texture)) {\n      if (!this._active) return false;\n      this._active = false;\n      this._flat.visible = true;\n      if (this._mesh) this._mesh.visible = false;\n      return false;\n    }\n\n    const mesh = this._ensureMesh(texture);\n    if (mesh.texture !== texture) mesh.texture = texture;\n    this._syncUvs(mesh, texture);\n    mesh.setCorners(quad.x0, quad.y0, quad.x1, quad.y1, quad.x2, quad.y2, quad.x3, quad.y3);\n    // Pivot and position on the quad's centre: renders unchanged at scale 1,\n    // but a scale tween pulses about the middle of the cell.\n    const cx = (quad.x0 + quad.x1 + quad.x2 + quad.x3) / 4;\n    const cy = (quad.y0 + quad.y1 + quad.y2 + quad.y3) / 4;\n    mesh.pivot.set(cx, cy);\n    mesh.position.set(cx, cy);\n\n    if (!this._active) {\n      this._active = true;\n      this._flat.visible = false;\n      mesh.visible = true;\n    }\n    return true;\n  }\n\n  /** Reset the mesh's own transform. Call from `stopAnimation` / `onDeactivate`. */\n  resetTransform(): void {\n    if (this._mesh) this._mesh.scale.set(1, 1);\n  }\n\n  /**\n   * Push a new texture through without re-projecting. For the pooled swap in\n   * `onActivate`, where the identity changes but the quad has not.\n   */\n  syncTexture(texture: Texture): void {\n    if (this._active && this._mesh && this._mesh.texture !== texture) {\n      this._mesh.texture = texture;\n      this._syncUvs(this._mesh, texture);\n    }\n  }\n\n  destroy(): void {\n    if (!this._mesh) return;\n    this._mesh.destroy();\n    this._mesh = null;\n    this._active = false;\n\n    this._uvScratch = null;\n    this._uvSource = null;\n  }\n\n  private _ensureMesh(texture: Texture): PerspectiveMesh {\n    if (this._mesh) return this._mesh;\n    this._mesh = new PerspectiveMesh({\n      texture,\n      verticesX: VERTICES,\n      verticesY: VERTICES,\n    });\n    // `PerspectivePlaneGeometry` warps VERTEX POSITIONS and leaves the UVs\n    // alone, so the atlas mapping is ours to own. One scratch buffer, rewritten\n    // from the grid on every texture swap rather than remapped in place, which\n    // would compound.\n    this._uvScratch = new Float32Array(VERTICES * VERTICES * 2);\n    this._view.addChild(this._mesh);\n    return this._mesh;\n  }\n\n  /**\n   * Fold the texture's atlas frame into the mesh's UVs.\n   *\n   * A `Sprite` gets this free; a `Mesh` does not. Its 0..1 UVs address the\n   * whole SHEET, so an unmapped mesh draws the entire atlas squeezed into one\n   * cell.\n   *\n   * `texture.uvs` carries the frame's corners in source space and PixiJS keeps\n   * it current, so interpolating between them is exact. `textureMatrix` is not\n   * necessarily updated for this texture yet, and yields out-of-range UVs that\n   * sample whatever is next door on the sheet.\n   */\n  private _syncUvs(mesh: PerspectiveMesh, texture: Texture): void {\n    if (this._uvSource === texture) return;\n    const out = this._uvScratch;\n    if (!out) return;\n    this._uvSource = texture;\n\n    // Grid coordinates from the vertex index, not read back off the geometry:\n    // the UV buffer is not necessarily populated at construction, and a\n    // snapshot of zeros points every vertex at the frame's first texel - a\n    // cell of flat colour. Row-major, matching PixiJS's own plane transform.\n    const { x0, y0, x1, y1, x2, y2, x3, y3 } = texture.uvs;\n    const span = VERTICES - 1;\n    for (let k = 0; k < out.length / 2; k++) {\n      const u = (k % VERTICES) / span;\n      const v = Math.floor(k / VERTICES) / span;\n      const tl = (1 - u) * (1 - v);\n      const tr = u * (1 - v);\n      const br = u * v;\n      const bl = (1 - u) * v;\n      out[k * 2] = tl * x0 + tr * x1 + br * x2 + bl * x3;\n      out[k * 2 + 1] = tl * y0 + tr * y1 + br * y2 + bl * y3;\n    }\n    mesh.geometry.uvs = out;\n  }\n}\n","import { Sprite, type Texture } from 'pixi.js';\nimport type { gsap } from 'gsap';\n\nimport type { ReelCellInset, ReelCellQuad } from '../config/types.js';\nimport { ReelSymbol } from './ReelSymbol.js';\nimport { PerspectiveCell, textureCellInset } from './PerspectiveCell.js';\n\nexport interface SpriteSymbolOptions {\n  /** Map of symbolId → Texture. */\n  textures: Record<string, Texture>;\n  /** Anchor point. Default: { x: 0.5, y: 0.5 }. */\n  anchor?: { x: number; y: number };\n}\n\n/**\n * Symbol implementation using a simple PixiJS Sprite.\n * Swaps texture on activate. Win animation is a scale pulse via GSAP.\n *\n * On a curved reel it draws the same texture through a `PerspectiveMesh`\n * instead, so the cell is a real projected trapezoid rather than a rectangle\n * that has been scaled down. See {@link ReelSymbol.applyCellQuad}.\n */\nexport class SpriteSymbol extends ReelSymbol {\n  private _sprite: Sprite;\n  private _textures: Record<string, Texture>;\n  private _winTween: gsap.core.Tween | null = null;\n  private _perspective: PerspectiveCell;\n\n  constructor(options: SpriteSymbolOptions) {\n    super();\n    this._textures = options.textures;\n    const anchor = options.anchor ?? { x: 0, y: 0 };\n    this._sprite = new Sprite();\n    this._sprite.anchor.set(anchor.x, anchor.y);\n    this.view.addChild(this._sprite);\n    this._perspective = new PerspectiveCell(this.view, this._sprite);\n  }\n\n  protected onActivate(symbolId: string): void {\n    const texture = this._textures[symbolId];\n    if (texture) {\n      this._sprite.texture = texture;\n      // The pool hands this instance a new identity without re-projecting, so\n      // the mesh has to be told about the swap or it keeps drawing the old one.\n      this._perspective.syncTexture(texture);\n    }\n  }\n\n  protected onDeactivate(): void {\n    this._killWinTween();\n    this._sprite.scale.set(1, 1);\n    this._perspective.resetTransform();\n  }\n\n  override get cellInset(): ReelCellInset | null {\n    return textureCellInset(this._sprite.texture);\n  }\n\n  override applyCellQuad(quad: ReelCellQuad | null): void {\n    if (this._perspective.apply(quad, this._sprite.texture)) {\n      // The mesh's own corners carry the whole projection, so the view must\n      // stay at identity. Anything else would apply the curve twice.\n      this.view.scale.set(1, 1);\n      this.view.pivot.set(0, 0);\n      return;\n    }\n    // The mesh declined this texture (today: any atlas sub-frame). Take the\n    // base class's uniform-scale fit so the cell is still on the drum.\n    super.applyCellQuad(quad);\n  }\n\n  async playWin(): Promise<void> {\n    this._killWinTween();\n    // Pulse whichever object is actually on screen. The mesh is pivoted on the\n    // quad's centre, so it swells in place the same way the sprite does.\n    const target = this._perspective.isActive ? this._perspective.mesh : this._sprite;\n    if (!target) return;\n    return new Promise<void>((resolve) => {\n      this._winTween = this.gsap.to(target.scale, {\n        x: 1.15,\n        y: 1.15,\n        duration: 0.15,\n        yoyo: true,\n        repeat: 1,\n        ease: 'power2.inOut',\n        onComplete: resolve,\n      });\n    });\n  }\n\n  stopAnimation(): void {\n    this._killWinTween();\n    this._sprite.scale.set(1, 1);\n    this._perspective.resetTransform();\n  }\n\n  resize(width: number, height: number): void {\n    this._sprite.width = width;\n    this._sprite.height = height;\n  }\n\n  protected override onDestroy(): void {\n    this._killWinTween();\n    this._perspective.destroy();\n  }\n\n  private _killWinTween(): void {\n    if (this._winTween) {\n      this._winTween.kill();\n      this._winTween = null;\n    }\n  }\n}\n","import { AnimatedSprite, type Texture } from 'pixi.js';\nimport type { ReelCellInset, ReelCellQuad } from '../config/types.js';\nimport { ReelSymbol } from './ReelSymbol.js';\nimport { PerspectiveCell, textureCellInset } from './PerspectiveCell.js';\n\nexport interface AnimatedSpriteSymbolOptions {\n  /** Map of symbolId → array of frame textures. */\n  frames: Record<string, Texture[]>;\n  /** Playback speed (frames per second multiplier). Default: 1. */\n  animationSpeed?: number;\n  /** Anchor point. Default: { x: 0.5, y: 0.5 }. */\n  anchor?: { x: number; y: number };\n}\n\n/**\n * Symbol implementation using PixiJS AnimatedSprite.\n * Swaps frame arrays on activate. Win animation plays the full sequence.\n */\nexport class AnimatedSpriteSymbol extends ReelSymbol {\n  private _animSprite: AnimatedSprite;\n  private _frames: Record<string, Texture[]>;\n  private _animationSpeed: number;\n  private _winResolve: (() => void) | null = null;\n  private _perspective: PerspectiveCell;\n\n  constructor(options: AnimatedSpriteSymbolOptions) {\n    super();\n    this._frames = options.frames;\n    this._animationSpeed = options.animationSpeed ?? 1;\n    const anchor = options.anchor ?? { x: 0, y: 0 };\n\n    const firstFrames = Object.values(this._frames)[0] ?? [];\n    this._animSprite = new AnimatedSprite(firstFrames.length > 0 ? firstFrames : []);\n    this._animSprite.anchor.set(anchor.x, anchor.y);\n    this._animSprite.animationSpeed = this._animationSpeed;\n    this._animSprite.loop = false;\n    this.view.addChild(this._animSprite);\n    this._perspective = new PerspectiveCell(this.view, this._animSprite);\n    // On a curved reel the mesh, not the sprite, is what draws. It has to be\n    // handed each new frame or a playing win animation freezes on frame 0.\n    this._animSprite.onFrameChange = () => {\n      this._perspective.syncTexture(this._animSprite.texture);\n    };\n  }\n\n  override get cellInset(): ReelCellInset | null {\n    return textureCellInset(this._animSprite.texture);\n  }\n\n  override applyCellQuad(quad: ReelCellQuad | null): void {\n    if (this._perspective.apply(quad, this._animSprite.texture)) {\n      // The mesh's own corners carry the whole projection, so the view must\n      // stay at identity. Anything else would apply the curve twice.\n      this.view.scale.set(1, 1);\n      this.view.pivot.set(0, 0);\n      return;\n    }\n    // The mesh declined this texture (today: any atlas sub-frame). Take the\n    // base class's uniform-scale fit so the cell is still on the drum.\n    super.applyCellQuad(quad);\n  }\n\n  protected onActivate(symbolId: string): void {\n    const frames = this._frames[symbolId];\n    if (frames && frames.length > 0) {\n      this._animSprite.textures = frames;\n      this._animSprite.gotoAndStop(0);\n    }\n  }\n\n  protected onDeactivate(): void {\n    this._animSprite.stop();\n    this._winResolve = null;\n  }\n\n  async playWin(): Promise<void> {\n    return new Promise<void>((resolve) => {\n      this._winResolve = resolve;\n      this._animSprite.loop = false;\n      this._animSprite.onComplete = () => {\n        this._winResolve = null;\n        this._animSprite.onComplete = undefined;\n        // Return to frame 0 so the cell settles on its idle look instead\n        // of holding the last frame of the win sequence (which for\n        // generated pixel-art sequences often ends mid-action. muted,\n        // shifted, or otherwise not the neutral base pose).\n        this._animSprite.gotoAndStop(0);\n        resolve();\n      };\n      this._animSprite.gotoAndPlay(0);\n    });\n  }\n\n  stopAnimation(): void {\n    this._animSprite.stop();\n    this._animSprite.gotoAndStop(0);\n    if (this._winResolve) {\n      this._winResolve();\n      this._winResolve = null;\n    }\n  }\n\n  resize(width: number, height: number): void {\n    this._animSprite.width = width;\n    this._animSprite.height = height;\n    // Position the sprite to match its anchor so it fills the cell.\n    // Without this, an anchor of (0.5, 0.5) would render the sprite with\n    // its centre at the cell's top-left corner. only the bottom-right\n    // quadrant visible inside the mask.\n    this._animSprite.x = width * this._animSprite.anchor.x;\n    this._animSprite.y = height * this._animSprite.anchor.y;\n  }\n\n  protected override onDestroy(): void {\n    this._animSprite.onFrameChange = undefined;\n    this._perspective.destroy();\n  }\n}\n","import { ReelSymbol } from './ReelSymbol.js';\n\n/**\n * A {@link ReelSymbol} that renders nothing and never animates.\n *\n * Register it for an id that needs to occupy a grid slot without producing\n * any visual - the blank rest state of a {@link HoldAndWinBoard} cell, a\n * cascade \"hole\", a dry-run symbol-set placeholder. The {@link HoldAndWinBuilder}\n * auto-registers one under its `emptyId` so callers never have to.\n */\nexport class EmptySymbol extends ReelSymbol {\n  protected onActivate(_symbolId: string): void {}\n  protected onDeactivate(): void {}\n  async playWin(): Promise<void> {}\n  stopAnimation(): void {}\n  resize(_width: number, _height: number): void {}\n}\n","import { BlurFilter, Container, Rectangle, Sprite, type Texture } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\n\n/**\n * The slice of a PixiJS renderer the cache needs. `Renderer` (WebGL or\n * WebGPU) satisfies it structurally; tests can pass a stub.\n */\nexport interface SnapshotRenderer {\n  generateTexture(options: {\n    target: Container;\n    frame?: Rectangle;\n    resolution?: number;\n    antialias?: boolean;\n  }): Texture;\n}\n\n/** Tuning for the baked motion-blur variant of a snapshot. */\nexport interface MotionBlurOptions {\n  /**\n   * Smear axis — the reel's travel direction. `StaticSpinSymbol` defaults\n   * this to the owning set's orientation (`'y'` vertical, `'x'` horizontal),\n   * so it is only worth setting to override that, or when calling the cache\n   * directly. The other axis is never blurred.\n   */\n  axis?: 'y' | 'x';\n  /**\n   * Blur strength in pixels along the axis. Default: 20% of the cell's\n   * span on that axis (`cellHeight * 0.2` for `'y'`, `cellWidth * 0.2`\n   * for `'x'`).\n   */\n  strength?: number;\n  /** BlurFilter quality (number of passes). Default: 4. */\n  quality?: number;\n  /**\n   * Extra transparent pixels added on both sides of the cell along the\n   * axis so the smear isn't clipped at the texture edge.\n   * Default: `ceil(strength)`.\n   */\n  padding?: number;\n}\n\nexport interface SpinTextureCacheOptions {\n  /** The renderer used to generate snapshot textures (`app.renderer`). */\n  renderer: SnapshotRenderer;\n  /** Resolution for generated textures. Default: the renderer's default. */\n  resolution?: number;\n  /** Default motion-blur tuning for `captureBlurred`. */\n  blur?: MotionBlurOptions;\n}\n\ninterface CacheEntry {\n  texture: Texture;\n  /** true = generated by this cache (destroyed on invalidate), false = user-provided. */\n  owned: boolean;\n  width: number;\n  height: number;\n  /** Smear axis the blurred variant was baked for (blurred entries only). */\n  axis?: 'y' | 'x';\n}\n\n/**\n * Per-symbolId cache of \"spin textures\". flat snapshots a reel shows\n * instead of the live symbol (Spine skeleton, animated sprite, ...) while\n * it spins.\n *\n * Two flavors per symbolId:\n *\n *   - **static**. the symbol rendered once into a RenderTexture at cell size.\n *   - **blurred**. the static snapshot smeared vertically through a one-time\n *     BlurFilter pass into a taller, padded RenderTexture. At spin time it's\n *     an ordinary sprite texture: zero filters per frame.\n *\n * Textures come from two sources, user-provided always winning:\n *\n *   - `setStatic(id, tex)` / `setBlurred(id, tex)`. hand-authored art from\n *     your atlas. Never destroyed by the cache.\n *   - `captureStatic` / `captureBlurred`. generated on demand from a live\n *     symbol view. Owned by the cache and destroyed on `invalidate` /\n *     `clear` / `destroy`.\n *\n * Captures are keyed by symbolId and remembered together with the cell size\n * they were taken at; capturing again at a different size regenerates.\n * Share ONE cache across all reels/symbols of a reel set. that's the point.\n */\nexport class SpinTextureCache implements Disposable {\n  private _renderer: SnapshotRenderer;\n  private _resolution: number | undefined;\n  private _blurDefaults: MotionBlurOptions;\n  private _static = new Map<string, CacheEntry>();\n  private _blurred = new Map<string, CacheEntry>();\n  private _isDestroyed = false;\n\n  constructor(options: SpinTextureCacheOptions) {\n    this._renderer = options.renderer;\n    this._resolution = options.resolution;\n    this._blurDefaults = options.blur ?? {};\n  }\n\n  // -- User-provided textures ----------------------------------------------\n\n  /** Provide a hand-authored static spin texture. Wins over captures. */\n  setStatic(symbolId: string, texture: Texture): void {\n    this._put(this._static, symbolId, { texture, owned: false, width: 0, height: 0 });\n  }\n\n  /** Provide a hand-authored motion-blur texture. Wins over captures. */\n  setBlurred(symbolId: string, texture: Texture): void {\n    this._put(this._blurred, symbolId, { texture, owned: false, width: 0, height: 0 });\n  }\n\n  // -- Lookups ---------------------------------------------------------------\n\n  getStatic(symbolId: string): Texture | null {\n    return this._static.get(symbolId)?.texture ?? null;\n  }\n\n  getBlurred(symbolId: string): Texture | null {\n    return this._blurred.get(symbolId)?.texture ?? null;\n  }\n\n  hasStatic(symbolId: string): boolean {\n    return this._static.has(symbolId);\n  }\n\n  hasBlurred(symbolId: string): boolean {\n    return this._blurred.has(symbolId);\n  }\n\n  // -- Capture ---------------------------------------------------------------\n\n  /**\n   * Snapshot `source` (a symbol's `view`, already activated and resized to\n   * the cell) into a `width`x`height` texture and cache it under `symbolId`.\n   * Returns the cached texture if one already exists at this size; a\n   * user-provided texture always short-circuits.\n   */\n  captureStatic(symbolId: string, source: Container, width: number, height: number): Texture {\n    const existing = this._static.get(symbolId);\n    if (existing && (!existing.owned || (existing.width === width && existing.height === height))) {\n      return existing.texture;\n    }\n    const texture = this._renderer.generateTexture({\n      target: source,\n      frame: new Rectangle(0, 0, width, height),\n      resolution: this._resolution,\n      antialias: true,\n    });\n    this._put(this._static, symbolId, { texture, owned: true, width, height });\n    return texture;\n  }\n\n  /**\n   * Bake a motion-blurred variant of the static snapshot for `symbolId`\n   * (which must exist. call `captureStatic` or `setStatic` first) and\n   * cache it. The smear runs along `blur.axis` — the reel's travel\n   * direction (`'y'` default when called directly; `StaticSpinSymbol`\n   * derives it from the set's orientation) — and the\n   * result is `2 * padding` larger than the cell on that axis only. Draw\n   * it center-anchored at the cell center and the smear extends evenly\n   * past the cell on both sides.\n   */\n  captureBlurred(\n    symbolId: string,\n    width: number,\n    height: number,\n    blur?: MotionBlurOptions,\n  ): Texture {\n    const axis = blur?.axis ?? this._blurDefaults.axis ?? 'y';\n    const existing = this._blurred.get(symbolId);\n    if (\n      existing &&\n      (!existing.owned ||\n        (existing.width === width && existing.height === height && existing.axis === axis))\n    ) {\n      return existing.texture;\n    }\n    const staticTex = this.getStatic(symbolId);\n    if (!staticTex) {\n      throw new Error(\n        `SpinTextureCache.captureBlurred('${symbolId}'): no static texture to blur. ` +\n          `Call captureStatic() or setStatic() for this symbolId first.`,\n      );\n    }\n\n    const strength =\n      blur?.strength ?? this._blurDefaults.strength ?? (axis === 'y' ? height : width) * 0.2;\n    const quality = blur?.quality ?? this._blurDefaults.quality ?? 4;\n    const padding = blur?.padding ?? this._blurDefaults.padding ?? Math.ceil(strength);\n\n    const wrap = new Container();\n    const sprite = new Sprite(staticTex);\n    // Fit the source into the cell box in case it's a user-provided static\n    // texture with different dimensions.\n    sprite.width = width;\n    sprite.height = height;\n    if (axis === 'y') {\n      sprite.y = padding;\n      sprite.filters = [new BlurFilter({ strengthX: 0, strengthY: strength, quality })];\n    } else {\n      sprite.x = padding;\n      sprite.filters = [new BlurFilter({ strengthX: strength, strengthY: 0, quality })];\n    }\n    wrap.addChild(sprite);\n\n    const texture = this._renderer.generateTexture({\n      target: wrap,\n      frame: new Rectangle(\n        0,\n        0,\n        width + (axis === 'x' ? padding * 2 : 0),\n        height + (axis === 'y' ? padding * 2 : 0),\n      ),\n      resolution: this._resolution,\n      antialias: true,\n    });\n    // Destroy the scratch scene but not the cached static texture.\n    wrap.destroy({ children: true });\n\n    this._put(this._blurred, symbolId, { texture, owned: true, width, height, axis });\n    return texture;\n  }\n\n  // -- Invalidation ------------------------------------------------------------\n\n  /** Drop (and destroy, if cache-generated) both textures for one symbolId. */\n  invalidate(symbolId: string): void {\n    this._drop(this._static, symbolId);\n    this._drop(this._blurred, symbolId);\n  }\n\n  /** Drop everything. Call when the cell size changes (responsive relayout). */\n  clear(): void {\n    for (const id of [...this._static.keys()]) this._drop(this._static, id);\n    for (const id of [...this._blurred.keys()]) this._drop(this._blurred, id);\n  }\n\n  destroy(): void {\n    if (this._isDestroyed) return;\n    this.clear();\n    this._isDestroyed = true;\n  }\n\n  get isDestroyed(): boolean {\n    return this._isDestroyed;\n  }\n\n  // -- Internals ----------------------------------------------------------------\n\n  private _put(map: Map<string, CacheEntry>, id: string, entry: CacheEntry): void {\n    this._drop(map, id);\n    map.set(id, entry);\n  }\n\n  private _drop(map: Map<string, CacheEntry>, id: string): void {\n    const existing = map.get(id);\n    if (existing?.owned) existing.texture.destroy(true);\n    map.delete(id);\n  }\n}\n\nexport interface PrewarmSpinTexturesOptions {\n  /** The shared cache to fill. */\n  cache: SpinTextureCache;\n  /** Every symbolId to bake. */\n  ids: string[];\n  /**\n   * Factory for a scratch symbol used as the render source. Typically the\n   * same factory you registered (e.g. `() => new SpineReelSymbol(opts)`).\n   * It is activated per id, snapshotted, and destroyed at the end.\n   */\n  createSymbol: () => ReelSymbol;\n  /** Cell width the reels will use. */\n  width: number;\n  /** Cell height the reels will use. */\n  height: number;\n  /** Also bake the motion-blur variants. Default: true. */\n  blurred?: boolean;\n  /** Motion-blur tuning override (defaults to the cache's). */\n  blur?: MotionBlurOptions;\n}\n\n/**\n * Bake spin textures for every symbolId up front (at load or between\n * rounds) so the first spin never pays a capture hitch. Ids that already\n * have a user-provided or captured texture are skipped by the cache.\n *\n * ```ts\n * prewarmSpinTextures({\n *   cache,\n *   ids: ['cherry', 'lemon', 'seven'],\n *   createSymbol: () => new SpineReelSymbol(spineOpts),\n *   width: 150,\n *   height: 150,\n * });\n * ```\n */\nexport function prewarmSpinTextures(options: PrewarmSpinTexturesOptions): void {\n  const { cache, ids, width, height } = options;\n  const blurred = options.blurred ?? true;\n  const symbol = options.createSymbol();\n  try {\n    for (const id of ids) {\n      if (symbol.symbolId !== id) symbol.activate(id);\n      symbol.resize(width, height);\n      cache.captureStatic(id, symbol.view, width, height);\n      if (blurred) cache.captureBlurred(id, width, height, options.blur);\n    }\n  } finally {\n    symbol.destroy();\n  }\n}\n","import { Sprite } from 'pixi.js';\nimport type { gsap } from 'gsap';\n\nimport { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { MotionBlurOptions, SpinTextureCache } from './SpinTextureCache.js';\n\nexport interface StaticSpinSymbolOptions {\n  /**\n   * Factory for the wrapped \"live\" symbol. any ReelSymbol subclass\n   * (SpineReelSymbol, AnimatedSpriteSymbol, a custom class). The wrapper\n   * owns the instance: it activates it while the reel is at rest and\n   * deactivates it while the reel spins.\n   */\n  createInner: () => ReelSymbol;\n  /** Shared spin-texture cache. one per reel set. */\n  cache: SpinTextureCache;\n  /**\n   * What to show while spinning. `'blurred'` (default) crossfades the\n   * static snapshot into the baked motion-blur variant; `'static'` shows\n   * the crisp snapshot only and never touches the blur pipeline.\n   */\n  spinTexture?: 'blurred' | 'static';\n  /**\n   * Crossfade duration (ms) from crisp snapshot to blurred snapshot at\n   * spin start. matches the reel's acceleration ramp. `0` = instant.\n   * Default: 120. Ignored when `spinTexture: 'static'`.\n   */\n  blurRampMs?: number;\n  /**\n   * Motion-blur tuning forwarded to `cache.captureBlurred`. `axis` defaults\n   * to the owning set's travel axis, so a horizontal set smears sideways\n   * without being told; pass it explicitly only to override that. The\n   * sideways travel (and snapshots fit the cell by height instead of width).\n   */\n  blur?: MotionBlurOptions;\n}\n\n/**\n * Wraps any `ReelSymbol` and swaps it for a cached snapshot texture while\n * the reel spins. \"spin static, not Spine.\"\n *\n * At rest the inner symbol is live (idle loops, win animations, cascade\n * destruction all delegate to it). When the reel starts, the wrapper\n * snapshots the inner symbol through the shared {@link SpinTextureCache}\n * (or reuses a cached / user-provided texture), **deactivates** the inner\n * symbol so it costs nothing, and spins a plain Sprite instead. Symbols\n * that wrap in mid-spin only retarget that sprite's texture. no inner\n * symbol is ever created or ticked for them. On land the inner symbol is\n * reactivated on the final symbolId.\n *\n * With `spinTexture: 'blurred'` the sprite crossfades from the crisp\n * snapshot to a pre-baked motion-blur variant over `blurRampMs`, then\n * spins the blurred texture. The smear follows the reel's travel axis,\n * derived from the set's orientation (override with `blur.axis`). No\n * filter runs during the spin; the blur is baked once per symbolId and\n * cached.\n *\n * Register it like any other symbol:\n *\n * ```ts\n * const cache = new SpinTextureCache({ renderer: app.renderer });\n * builder.symbols((r) => {\n *   for (const id of ids) {\n *     r.register(id, StaticSpinSymbol, {\n *       createInner: () => new SpineReelSymbol(spineOpts),\n *       cache,\n *     });\n *   }\n * });\n * ```\n *\n * Prefer calling {@link prewarmSpinTextures} at load time so the first\n * spin doesn't pay the one-time capture cost.\n */\nexport class StaticSpinSymbol extends ReelSymbol {\n  private _inner: ReelSymbol;\n  private _cache: SpinTextureCache;\n  private _mode: 'blurred' | 'static';\n  private _rampMs: number;\n  private _blurOpts: MotionBlurOptions | undefined;\n\n  private _staticSprite: Sprite;\n  private _blurSprite: Sprite;\n  private _spinning = false;\n  private _anticipating = false;\n  private _cellW = 0;\n  private _cellH = 0;\n  private _rampTween: gsap.core.Tween | null = null;\n\n  constructor(options: StaticSpinSymbolOptions) {\n    super();\n    this._inner = options.createInner();\n    this._cache = options.cache;\n    this._mode = options.spinTexture ?? 'blurred';\n    this._rampMs = options.blurRampMs ?? 120;\n    this._blurOpts = options.blur;\n\n    this.view.addChild(this._inner.view);\n    this._staticSprite = new Sprite();\n    this._staticSprite.anchor.set(0.5, 0.5);\n    this._staticSprite.visible = false;\n    this._blurSprite = new Sprite();\n    this._blurSprite.anchor.set(0.5, 0.5);\n    this._blurSprite.visible = false;\n    this.view.addChild(this._staticSprite, this._blurSprite);\n  }\n\n  /** The wrapped live symbol. for type-specific access (`inner as SpineReelSymbol`). */\n  get inner(): ReelSymbol {\n    return this._inner;\n  }\n\n  /** True while the wrapper is showing a snapshot instead of the live symbol. */\n  get isShowingSnapshot(): boolean {\n    return this._spinning;\n  }\n\n  protected onActivate(symbolId: string): void {\n    if (this._spinning) {\n      // Wrapped in mid-spin: never touch the inner symbol, just retarget\n      // the snapshot sprite (instantly blurred. the ramp belongs to spin\n      // start, not to cells joining a reel already at speed).\n      this._showSnapshot(symbolId, { instant: true });\n      return;\n    }\n    if (this._inner.symbolId !== symbolId) this._inner.activate(symbolId);\n    this._hideSnapshot();\n  }\n\n  protected onDeactivate(): void {\n    this._killRamp();\n    this._spinning = false;\n    this._anticipating = false;\n    this._staticSprite.visible = false;\n    this._blurSprite.visible = false;\n    if (this._inner.symbolId !== '') this._inner.deactivate();\n  }\n\n  override onReelSpinStart(joinedMidSpin = false): void {\n    if (this._spinning) {\n      // Idempotent: a repeat notification (same-id fast path during a\n      // wrap) only refreshes the snapshot target.\n      this._showSnapshot(this.symbolId, { instant: true });\n      return;\n    }\n    this._spinning = true;\n    this._anticipating = false;\n    // Capture while the inner symbol is still live so a cache miss can\n    // snapshot it in place, then put the inner symbol to sleep for the\n    // duration of the spin.\n    this._showSnapshot(this.symbolId, { instant: joinedMidSpin || this._rampMs <= 0 });\n    if (this._inner.symbolId !== '') this._inner.deactivate();\n  }\n\n  /**\n   * The reel entered its anticipation tease: it is slow enough to read, so\n   * crossfade the baked blur back out and ride the crisp snapshot until\n   * the land. Mid-tease installs arrive blurred (`_showSnapshot` instant)\n   * and immediately relax through this same hook.\n   */\n  override onReelAnticipationStart(): void {\n    if (!this._spinning || this._anticipating) return;\n    this._anticipating = true;\n    if (this._mode === 'static') return;\n\n    this._killRamp();\n    this._staticSprite.visible = true;\n    this._staticSprite.alpha = 1;\n    if (!this._blurSprite.visible || this._rampMs <= 0) {\n      this._blurSprite.visible = false;\n      return;\n    }\n    this._rampTween = this.gsap.to(this._blurSprite, {\n      alpha: 0,\n      duration: this._rampMs / 1000,\n      ease: 'power1.out',\n      onComplete: () => {\n        this._rampTween = null;\n        this._blurSprite.visible = false;\n      },\n    });\n  }\n\n  override onReelSpinEnd(): void {\n    if (!this._spinning) return;\n    this._spinning = false;\n    this._anticipating = false;\n    this._killRamp();\n    this._staticSprite.visible = false;\n    this._blurSprite.visible = false;\n    if (this._inner.symbolId !== this.symbolId) {\n      this._inner.activate(this.symbolId);\n      this._inner.resize(this._cellW, this._cellH);\n    }\n  }\n\n  override onReelLanded(): void {\n    if (!this._spinning) this._inner.onReelLanded();\n  }\n\n  async playWin(): Promise<void> {\n    return this._inner.playWin();\n  }\n\n  stopAnimation(): void {\n    if (this._inner.symbolId !== '') this._inner.stopAnimation();\n  }\n\n  override async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n    // Landed symbols delegate so art-specific destruction (Spine `out`\n    // tracks etc.) plays; a snapshot mid-spin falls back to the generic\n    // implode on the wrapper view.\n    if (!this._spinning && this._inner.symbolId !== '') return this._inner.playDestroy(opts);\n    return super.playDestroy(opts);\n  }\n\n  resize(width: number, height: number): void {\n    this._cellW = width;\n    this._cellH = height;\n    this._staticSprite.position.set(width / 2, height / 2);\n    this._blurSprite.position.set(width / 2, height / 2);\n    this._fitSprites();\n    this._inner.resize(width, height);\n  }\n\n  protected override onDestroy(): void {\n    this._killRamp();\n    // Detach before destroying so the base class's view.destroy({children})\n    // doesn't double-destroy the inner view.\n    if (this._inner.view.parent === this.view) this.view.removeChild(this._inner.view);\n    this._inner.destroy();\n  }\n\n  // -- Internals -------------------------------------------------------------\n\n  /**\n   * Point the snapshot sprites at `symbolId`'s cached textures, capturing\n   * on miss. With `instant`, the end state is applied directly (blurred\n   * fully visible); otherwise the crisp→blur crossfade ramp starts.\n   */\n  private _showSnapshot(symbolId: string, opts: { instant: boolean }): void {\n    const staticTex = this._ensureStatic(symbolId);\n    this._staticSprite.texture = staticTex;\n\n    if (this._mode === 'blurred') {\n      this._blurSprite.texture = this._cache.captureBlurred(\n        symbolId,\n        this._cellW,\n        this._cellH,\n        this._resolvedBlur(),\n      );\n    }\n    this._fitSprites();\n\n    if (this._mode === 'static') {\n      this._staticSprite.visible = true;\n      this._staticSprite.alpha = 1;\n      return;\n    }\n\n    if (opts.instant) {\n      // Anticipating: the tease shows the crisp snapshot. never bring the\n      // blur back for re-notifications or same-id wraps mid-tease.\n      if (this._anticipating) {\n        this._killRamp();\n        this._blurSprite.visible = false;\n        this._staticSprite.visible = true;\n        this._staticSprite.alpha = 1;\n        return;\n      }\n      // Already ramping? Let the running crossfade finish on the new\n      // textures instead of snapping mid-fade.\n      if (this._rampTween) return;\n      this._staticSprite.visible = false;\n      this._blurSprite.visible = true;\n      this._blurSprite.alpha = 1;\n      return;\n    }\n\n    this._killRamp();\n    this._staticSprite.visible = true;\n    this._staticSprite.alpha = 1;\n    this._blurSprite.visible = true;\n    this._blurSprite.alpha = 0;\n    this._rampTween = this.gsap.to(this._blurSprite, {\n      alpha: 1,\n      duration: this._rampMs / 1000,\n      ease: 'power1.in',\n      onComplete: () => {\n        this._rampTween = null;\n        this._staticSprite.visible = false;\n      },\n    });\n  }\n\n  /**\n   * Static texture for `symbolId`, capturing from the inner symbol on a\n   * cache miss. If the inner symbol currently holds a different identity\n   * (mid-spin wrap), it is briefly activated offscreen for the capture and\n   * deactivated again. one-time cost per symbolId; avoid it entirely with\n   * `prewarmSpinTextures`.\n   */\n  private _ensureStatic(symbolId: string) {\n    const cached = this._cache.getStatic(symbolId);\n    if (cached) return cached;\n\n    const needsTempActivation = this._inner.symbolId !== symbolId;\n    if (needsTempActivation) {\n      this._inner.activate(symbolId);\n      this._inner.resize(this._cellW, this._cellH);\n    }\n    const tex = this._cache.captureStatic(symbolId, this._inner.view, this._cellW, this._cellH);\n    if (needsTempActivation && this._spinning) this._inner.deactivate();\n    return tex;\n  }\n\n  private _hideSnapshot(): void {\n    this._killRamp();\n    this._staticSprite.visible = false;\n    this._blurSprite.visible = false;\n  }\n\n  /**\n   * Uniformly fit both sprites to the cell along the axis the blur does\n   * NOT pad (keeps aspect; the padded axis then centers out evenly past\n   * the cell). Vertical reels fit by width; horizontal strips fit by\n   * height.\n   */\n  private _fitSprites(): void {\n    if (this._cellW <= 0 || this._cellH <= 0) return;\n    const fitByHeight = this._resolvedBlur().axis === 'x';\n    for (const sprite of [this._staticSprite, this._blurSprite]) {\n      const tw = sprite.texture.width;\n      const th = sprite.texture.height;\n      if (tw <= 0 || th <= 0) continue;\n      sprite.scale.set(fitByHeight ? this._cellH / th : this._cellW / tw);\n    }\n  }\n\n  /**\n   * Blur options with the smear axis resolved.\n   *\n   * The smear must follow the strip, so the default comes from the owning\n   * set's orientation rather than being hardcoded to `'y'`. Before this,\n   * a horizontal set using `StaticSpinSymbol` smeared vertically - across\n   * the direction of travel - and nothing said so. An explicit\n   * `blur.axis` still wins, for art that wants a deliberate cross-smear.\n   */\n  private _resolvedBlur(): MotionBlurOptions {\n    return { ...this._blurOpts, axis: this._blurOpts?.axis ?? this.mainAxis };\n  }\n\n  private _killRamp(): void {\n    if (this._rampTween) {\n      this._rampTween.kill();\n      this._rampTween = null;\n    }\n  }\n}\n","import { Graphics, Text } from 'pixi.js';\nimport { ReelSymbol } from './ReelSymbol.js';\n\n/**\n * **Debug / prototyping symbol - NOT for production.**\n *\n * `CardSymbol` is a flat-rectangle-plus-centered-text `ReelSymbol` drawn\n * entirely with `PIXI.Graphics`. It exists to give recipes, demos, and\n * mechanic tests a no-asset, no-loader, infinitely-resizable visual that\n * always renders crisply at any cell size, showing how the engine treats\n * cell space across MultiWays reshapes, big-symbol blocks, and pyramid\n * layouts.\n *\n * In a real game, ship one of:\n *\n *   - `SpriteSymbol` - pre-rendered art at one resolution. Cheapest.\n *   - `AnimatedSpriteSymbol` - frame-by-frame win/idle animation.\n *   - `SpineSymbol` (via `pixi-reels/spine`) - vector skeletal animation\n *     that scales without quality loss. Best for MultiWays where cells\n *     resize across spins.\n *   - Or a custom `ReelSymbol` subclass for game-specific visuals.\n *\n * Ships from the package so a prototype runs with no art at all. It is\n * deliberately plain `Graphics`: swap it for real art before you ship.\n *\n * ```ts\n * import { CardSymbol, CARD_DECK } from 'pixi-reels';\n *\n * builder.symbols((registry) => {\n *   for (const card of CARD_DECK) {\n *     registry.register(card.id, CardSymbol, { color: card.color, label: card.label });\n *   }\n * });\n * ```\n */\nexport interface CardSymbolOptions {\n  /** Hex fill color (e.g. `0xc0392b`). */\n  color: number;\n  /** Centered label text (e.g. `'A'`, `'10'`, `'WILD'`). */\n  label: string;\n  /** Text fill color. Defaults to white. */\n  textColor?: number;\n}\n\nexport class CardSymbol extends ReelSymbol {\n  private _color: number;\n  private _label: string;\n  private _textColor: number;\n  private _gfx: Graphics;\n  private _text: Text;\n\n  constructor(opts: CardSymbolOptions) {\n    super();\n    this._color = opts.color;\n    this._label = opts.label;\n    this._textColor = opts.textColor ?? 0xffffff;\n    this._gfx = new Graphics();\n    this._text = new Text({\n      text: this._label,\n      style: {\n        // Roboto Condensed if installed (the site loads it via @fontsource);\n        // falls through to a system condensed sans-serif on plain pages.\n        fontFamily:\n          '\"Roboto Condensed\", \"Arial Narrow\", \"Helvetica Neue Condensed\", \"Liberation Sans Narrow\", system-ui, sans-serif',\n        fontSize: 32,\n        fontWeight: '700',\n        fill: this._textColor,\n        align: 'center',\n      },\n    });\n    this._text.anchor.set(0.5);\n    this.view.addChild(this._gfx);\n    this.view.addChild(this._text);\n  }\n\n  protected onActivate(_symbolId: string): void {\n    // No-op - this symbol's visual identity is set at construction.\n  }\n\n  protected onDeactivate(): void {\n    // No-op - leave _gfx/_text in their last state until next activate.\n  }\n\n  async playWin(): Promise<void> {\n    return new Promise((resolve) => {\n      // Animate only the glyph - the card body stays put. Useful for big\n      // symbols (the rectangle is the cell footprint) and any setting\n      // where movement of the cell border is distracting (cascade refills,\n      // MultiWays adjust). The text is anchored at (0.5, 0.5) so its\n      // scale tween pivots about the centre.\n      this.gsap.killTweensOf(this._text);\n      this.gsap.killTweensOf(this._text.scale);\n      const originalFill = this._textColor;\n      const tl = this.gsap.timeline({\n        onComplete: () => {\n          this._text.scale.set(1, 1);\n          this._text.rotation = 0;\n          this._text.style.fill = originalFill;\n          resolve();\n        },\n      });\n      tl.to(this._text.scale, { x: 1.4, y: 1.4, duration: 0.18, ease: 'back.out(2)' }, 0)\n        .to(this._text, { rotation: -0.12, duration: 0.09, ease: 'sine.inOut' }, 0)\n        .to(this._text, { rotation: 0.12, duration: 0.18, ease: 'sine.inOut' }, 0.09)\n        .to(this._text, { rotation: 0, duration: 0.09, ease: 'sine.inOut' }, 0.27)\n        .to(this._text.scale, { x: 1, y: 1, duration: 0.18, ease: 'power2.out' }, 0.32);\n      // Glyph fill flashes warm gold mid-pulse - `Text.style.fill` isn't\n      // a tweenable target on every Pixi version, so we set it discretely.\n      this.gsap.delayedCall(0.06, () => (this._text.style.fill = 0xffe168));\n      this.gsap.delayedCall(0.42, () => (this._text.style.fill = originalFill));\n    });\n  }\n\n  stopAnimation(): void {\n    this.gsap.killTweensOf(this._text);\n    this.gsap.killTweensOf(this._text.scale);\n    this._text.scale.set(1, 1);\n    this._text.rotation = 0;\n    this._text.style.fill = this._textColor;\n    this._gfx.alpha = 1;\n  }\n\n  resize(width: number, height: number): void {\n    this._gfx.clear();\n    this._gfx.rect(0, 0, width, height).fill({ color: this._color });\n    // Inner darker stroke so adjacent cells are visually separated even\n    // when they share the same color.\n    this._gfx.rect(1, 1, width - 2, height - 2).stroke({ color: 0x000000, width: 2, alpha: 0.25 });\n    this._text.x = width / 2;\n    this._text.y = height / 2;\n    // Font size constrained by both height (~38%) and label width (so\n    // multi-char labels like '10' or 'WILD' don't overflow narrow cells).\n    // Smaller than half-cell to leave breathing room - looks less crowded.\n    const labelLen = Math.max(1, this._label.length);\n    const fitH = height * 0.38;\n    // Roboto Condensed glyphs are narrower (~0.45 avg ratio) than serif fonts.\n    const fitW = (width * 0.7) / (labelLen * 0.45);\n    this._text.style.fontSize = Math.max(7, Math.floor(Math.min(fitH, fitW)));\n  }\n}\n\n/**\n * Standard high-card deck for prototyping. Each card has its own color\n * so a full grid is visually unambiguous at a glance.\n */\nexport const CARD_DECK: ReadonlyArray<{ id: string; color: number; label: string }> = [\n  { id: '7',  color: 0xc0392b, label: '7' },   // crimson\n  { id: '8',  color: 0xe67e22, label: '8' },   // orange\n  { id: '9',  color: 0xf1c40f, label: '9' },   // amber\n  { id: '10', color: 0x27ae60, label: '10' },  // green\n  { id: 'J',  color: 0x16a085, label: 'J' },   // teal\n  { id: 'Q',  color: 0x2980b9, label: 'Q' },   // blue\n  { id: 'K',  color: 0x8e44ad, label: 'K' },   // purple\n  { id: 'A',  color: 0x2c3e50, label: 'A' },   // navy\n];\n\n/** A pale-yellow `WILD` card for sticky/expanding-wild prototypes. */\nexport const WILD_CARD = {\n  id: 'wild',\n  color: 0xfff3a0,\n  label: 'WILD',\n  textColor: 0x6b5400,\n} as const;\n","import type { ColumnTarget } from '../frame/ColumnTarget.js';\n\nexport interface ScatterAnticipationOptions {\n  /** The trigger symbol id to scan for (e.g. `'SCAT'`). */\n  symbol: string;\n  /**\n   * How many of `symbol` must be showing before anticipation kicks in.\n   * Anticipation begins on the reel AFTER the one that lands the\n   * `trigger`-th symbol. Default `2` (the classic \"2 scatters and the rest\n   * of the reels start teasing\").\n   */\n  trigger?: number;\n  /**\n   * Which of the remaining reels should tease:\n   *   - `'all-remaining'` (default): every reel after the trigger reel teases,\n   *     whether or not it actually holds the symbol. Maximum tension.\n   *   - `'scatter-only'`: only reels that actually contain the symbol tease.\n   *     Use when a result can't complete the feature on some reels and you\n   *     don't want to fake tension on reels that don't matter (e.g. a grid\n   *     with only 3 scatters total shouldn't slow the empty reels between/after\n   *     them).\n   */\n  mode?: 'all-remaining' | 'scatter-only';\n}\n\n/**\n * Decide which reels should show anticipation, straight from a result grid.\n *\n * Scans reels left→right counting `symbol` occurrences; once the running total\n * reaches `trigger`, the reels AFTER that point become the anticipation set\n * (per `mode`). Feed the return value straight into\n * `reelSet.setAnticipation(...)`.\n *\n * Returns an empty array when the grid never reaches `trigger` (no tease).\n *\n * `grid` is the same `ColumnTarget[]` you pass to `reelSet.setResult(...)`, so\n * you can hand the result straight through. Only visible cells are scanned.\n * buffer symbols are off-screen and never trigger tension.\n *\n * @example\n * // Result has SCAT on reels 0 and 1 → tease reels [2,3,4].\n * const grid = [{ visible: ['A','SCAT','B'] }, { visible: ['SCAT','A','B'] }, ...];\n * const reels = anticipationForScatters(grid, { symbol: 'SCAT', trigger: 2 });\n * reelSet.setResult(grid);\n * reelSet.setAnticipation(reels, { stagger: 'sequential', slowdown: { from: 0.4, to: 0.1 } });\n *\n * @example\n * // Only 3 scatters total (reels 0,1,3): 'scatter-only' teases just reel [3],\n * // leaving reels 2 and 4 to stop normally.\n * const reels = anticipationForScatters(grid, { symbol: 'SCAT', mode: 'scatter-only' });\n */\nexport function anticipationForScatters(\n  grid: ColumnTarget[],\n  opts: ScatterAnticipationOptions,\n): number[] {\n  const trigger = opts.trigger ?? 2;\n  const mode = opts.mode ?? 'all-remaining';\n\n  // Count the trigger symbol per reel. Only visible cells are scanned; buffer\n  // symbols are off-screen and must not trigger tension.\n  const perReelCount = grid.map(\n    (reel) => reel.visible.filter((id) => id === opts.symbol).length,\n  );\n\n  // Find the reel at which the running total first reaches `trigger`.\n  let running = 0;\n  let triggerReel = -1;\n  for (let i = 0; i < perReelCount.length; i++) {\n    running += perReelCount[i];\n    if (running >= trigger) {\n      triggerReel = i;\n      break;\n    }\n  }\n  if (triggerReel === -1) return [];\n\n  const result: number[] = [];\n  for (let i = triggerReel + 1; i < grid.length; i++) {\n    if (mode === 'all-remaining' || perReelCount[i] > 0) result.push(i);\n  }\n  return result;\n}\n","import type { SpinningMode } from './SpinningMode.js';\n\n/**\n * Cascade/tumble spinning mode.\n * Symbols fall from above with gravity-like acceleration,\n * used for tumble/avalanche mechanics.\n */\nexport class CascadeMode implements SpinningMode {\n  readonly name = 'cascade';\n\n  private _gravity: number;\n\n  /**\n   * @param gravity - Gravity acceleration factor. Default: 1.5.\n   */\n  constructor(gravity: number = 1.5) {\n    this._gravity = gravity;\n  }\n\n  computeDelta(slotPitch: number, speed: number, deltaMs: number): number {\n    const raw = (slotPitch * speed * this._gravity * deltaMs) / 1000;\n    // Full-slot cap. The old wrap-skip risk (contract L7) is gone now that\n    // ReelMotion derives rotation from total travel, so this only bounds the\n    // per-frame step; cascade speed is never negative.\n    return Math.min(raw, slotPitch);\n  }\n}\n","import type { SpinningMode } from './SpinningMode.js';\n\n/**\n * Immediate placement mode for super-turbo skip.\n * Symbols snap to position instantly. no actual spinning animation.\n */\nexport class ImmediateMode implements SpinningMode {\n  readonly name = 'immediate';\n\n  computeDelta(_slotPitch: number, _speed: number, _deltaMs: number): number {\n    // No movement. placement is handled directly by the phase\n    return 0;\n  }\n}\n","import { Container, Graphics } from 'pixi.js';\nimport type { Ticker } from 'pixi.js';\nimport { ReelSetBuilder } from '../core/ReelSetBuilder.js';\nimport type { Direction, Orientation } from '../core/ReelAxis.js';\nimport type { ReelSet } from '../core/ReelSet.js';\nimport { SharedRectMaskStrategy } from '../core/ReelViewport.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport { EmptySymbol } from '../symbols/EmptySymbol.js';\nimport { SpeedPresets } from '../config/SpeedPresets.js';\nimport type { SpeedProfile, SymbolData } from '../config/types.js';\nimport type { Disposable } from '../utils/Disposable.js';\n\n/** A cell coordinate in the grid. */\nexport interface BoardCell {\n  reel: number;\n  cell: number;\n}\n\n/** A landing target: spin `cell` and stop it showing `id`. */\nexport interface BoardSpinTarget {\n  cell: BoardCell;\n  id: string;\n}\n\n/** A speed profile, or a per-cell function of one (e.g. a stagger wave). */\nexport type BoardProfile = SpeedProfile | ((cell: BoardCell) => SpeedProfile);\n\nexport interface BoardGridOptions {\n  /** Grid dimensions. */\n  cols: number;\n  rows: number;\n  /** Cell edge length in pixels. */\n  cellSize: number;\n  /** Gap between cells. Default 4. */\n  gap?: number;\n  /** Id a cell shows when blank - also placed in the off-window buffers. Default `'empty'`. */\n  emptyId?: string;\n  /** Register symbol classes, exactly like `ReelSetBuilder.symbols`. Applied to every cell. */\n  symbols: (registry: SymbolRegistry) => void;\n  /** Strip weights during the spin. */\n  weights?: Record<string, number>;\n  /** Per-symbol engine overrides, exactly like `ReelSetBuilder.symbolData`. */\n  symbolData?: Record<string, Partial<SymbolData>>;\n  /** Injected RNG for the spin strips (deterministic demos / tests). */\n  rng?: () => number;\n  /** Drives every cell's reel - required. */\n  ticker: Ticker;\n  /** Per-cell background, drawn behind each reel. */\n  chrome?: (g: Graphics, size: number) => void;\n  /**\n   * Which way each cell's own strip travels while it spins. Every cell is a\n   * 1x1 reel set, so this changes the direction a symbol scrolls in from, not\n   * the board layout - `cols` and `rows` stay board dimensions either way.\n   * Defaults to the engine default (vertical / forward), i.e. symbols drop in\n   * from above.\n   */\n  orientation?: Orientation;\n  direction?: Direction;\n  /**\n   * Named speed profiles, each registered on every cell and selected by name\n   * via {@link BoardGrid.setProfile}. A value may be a flat profile or a\n   * per-cell function (for stagger waves). Defaults to a single `'default'`\n   * profile, which is the active one until you `setProfile` otherwise.\n   */\n  profiles?: Record<string, BoardProfile>;\n}\n\nconst key = (c: BoardCell): string => `${c.reel},${c.cell}`;\nconst DEFAULT_PROFILE = 'default';\n\n/**\n * A grid of cells that each spin **independently** - the generic \"board of\n * reels\" primitive. Every cell is its own 1×1 {@link ReelSet}, so it inherits\n * the engine's phases, speed modes and pooling rather than a parallel lighter\n * reel.\n *\n * Deliberately mechanism-only: it knows nothing about coins, locks, respins,\n * value or any game rule. It lays the grid out, hands back per-cell geometry\n * and live symbol instances, places symbols instantly, and spins a\n * **caller-chosen** set of cells to caller-chosen results. Build your own\n * feature on top by owning the rules in your own code; {@link HoldAndWinBoard}\n * is one such opinionated layer, built entirely on this public surface.\n *\n * ```ts\n * const grid = new BoardGrid({\n *   cols: 3, rows: 3, cellSize: 80,\n *   symbols: (r) => r.register('prize', PrizeSymbol, {}),\n *   weights: { prize: 1, empty: 4 },\n *   ticker: app.ticker,\n * });\n * app.stage.addChild(grid.container);\n *\n * await grid.spinCells(\n *   grid.cells().map((cell) => ({ cell, id: pick() })),  // you decide each result\n *   (cell, id) => console.log('landed', cell, id),       // react as each settles\n * );\n * ```\n */\nexport class BoardGrid implements Disposable {\n  readonly container: Container;\n  readonly cols: number;\n  readonly rows: number;\n  readonly cellSize: number;\n  readonly gap: number;\n  readonly emptyId: string;\n\n  private readonly _reels = new Map<string, ReelSet>();\n  private readonly _cells: BoardCell[] = [];\n  private _destroyed = false;\n\n  constructor(opts: BoardGridOptions) {\n    if (!opts.ticker) throw new Error('BoardGrid: a ticker is required.');\n    this.cols = opts.cols;\n    this.rows = opts.rows;\n    this.cellSize = opts.cellSize;\n    this.gap = opts.gap ?? 4;\n    this.emptyId = opts.emptyId ?? 'empty';\n    this.container = new Container();\n\n    const profiles: Record<string, BoardProfile> =\n      opts.profiles && Object.keys(opts.profiles).length > 0\n        ? opts.profiles\n        : { [DEFAULT_PROFILE]: { ...SpeedPresets.NORMAL, minimumSpinTime: 320 } };\n    const profileNames = Object.keys(profiles);\n    const profileFor = (p: BoardProfile, cell: BoardCell): SpeedProfile =>\n      typeof p === 'function' ? p(cell) : p;\n\n    for (let reel = 0; reel < opts.cols; reel++) {\n      for (let rowIdx = 0; rowIdx < opts.rows; rowIdx++) {\n        const cell: BoardCell = { reel, cell: rowIdx };\n        const origin = this._origin(cell);\n        if (opts.chrome) {\n          const bg = new Graphics();\n          opts.chrome(bg, this.cellSize);\n          bg.position.set(origin.x, origin.y);\n          this.container.addChild(bg);\n        }\n\n        const builder = new ReelSetBuilder()\n          .reels(1)\n          .visibleCells(1)\n          .symbolSize(this.cellSize, this.cellSize)\n          .symbolGap(0, 0)\n          // Spine symbols overrun the default per-reel rect mask; a shared rect\n          // keeps buffer-cell art from painting over neighbouring cells.\n          .maskStrategy(new SharedRectMaskStrategy())\n          .symbols((registry) => {\n            opts.symbols(registry);\n            if (!registry.has(this.emptyId)) registry.register(this.emptyId, EmptySymbol, {});\n          })\n          .initialFrame([\n            { visible: [this.emptyId], bufferStart: [this.emptyId], bufferEnd: [this.emptyId] },\n          ])\n          .ticker(opts.ticker)\n          .orientation(opts.orientation ?? 'vertical')\n          .direction(opts.direction ?? 'forward')\n          // The active profile defaults to the engine's 'normal'; point it at\n          // the first registered name so any profile vocabulary works.\n          .initialSpeed(profileNames[0]);\n        for (const [name, profile] of Object.entries(profiles)) {\n          builder.speed(name, profileFor(profile, cell));\n        }\n        if (opts.weights) builder.weights(opts.weights);\n        if (opts.symbolData) builder.symbolData(opts.symbolData);\n        if (opts.rng) builder.rng(opts.rng);\n\n        const reelSet = builder.build();\n        reelSet.position.set(origin.x, origin.y);\n        this.container.addChild(reelSet);\n        this._reels.set(key(cell), reelSet);\n        this._cells.push(cell);\n      }\n    }\n  }\n\n  /** Every cell coordinate, reel-major: (0,0), (0,1), ... then (1,0). */\n  cells(): BoardCell[] {\n    return this._cells.map((c) => ({ reel: c.reel, cell: c.cell }));\n  }\n\n  /** Board-local bounds of a cell. `container.toGlobal` for stage space. */\n  cellBounds(cell: BoardCell): { x: number; y: number; width: number; height: number } {\n    const origin = this._origin(cell);\n    return { x: origin.x, y: origin.y, width: this.cellSize, height: this.cellSize };\n  }\n\n  /** Board-local center of a cell - flight / trail start and end points. */\n  cellCenter(cell: BoardCell): { x: number; y: number } {\n    const origin = this._origin(cell);\n    return { x: origin.x + this.cellSize / 2, y: origin.y + this.cellSize / 2 };\n  }\n\n  /** Live symbol instance currently shown in a cell. */\n  symbolAt(cell: BoardCell): ReelSymbol {\n    return this._reel(cell).getReel(0).getSymbolAt(0);\n  }\n\n  /** The cell's underlying 1×1 ReelSet, for driving one cell directly. */\n  reelAt(cell: BoardCell): ReelSet {\n    return this._reel(cell);\n  }\n\n  /** Select a registered speed profile by name for one cell. */\n  setProfile(cell: BoardCell, name: string): void {\n    this._reel(cell).speed.set(name);\n  }\n\n  /** Place a symbol instantly (no spin), with blank off-window buffers. */\n  place(cell: BoardCell, id: string): void {\n    // Explicit empties in both buffers keep the rest state from\n    // random-filling past the mask.\n    this._reel(cell).getReel(0).placeSymbols({\n      visible: [id],\n      bufferStart: [this.emptyId],\n      bufferEnd: [this.emptyId],\n    });\n  }\n\n  /**\n   * Spin each target cell and stop it showing its `id`; `onLanded` fires per\n   * cell as it settles, in stagger order. The caller selects which cells spin\n   * and to what - this layer applies no lock/free policy of its own. Set\n   * profiles via {@link setProfile} first.\n   *\n   * `onLanded` may be **async**: if it returns a promise, that cell's task\n   * awaits it, so the returned promise resolves only once every cell has landed\n   * *and* its after-land work has finished. Cells still run concurrently, so an\n   * early cell's reveal overlaps with later cells still spinning.\n   */\n  async spinCells(\n    targets: BoardSpinTarget[],\n    onLanded: (cell: BoardCell, id: string) => void | Promise<void> = () => {},\n  ): Promise<void> {\n    await Promise.all(\n      targets.map(async ({ cell, id }) => {\n        const reelSet = this._reel(cell);\n        const settle = reelSet.spin();\n        // Buffers land empty too: off-window art can paint over neighbours.\n        reelSet.setResult([\n          { visible: [id], bufferStart: [this.emptyId], bufferEnd: [this.emptyId] },\n        ]);\n        await settle;\n        await onLanded(cell, id);\n      }),\n    );\n  }\n\n  /** Slam every in-flight cell to its landed position. Returns the count. */\n  skipSpinning(): number {\n    let inFlight = 0;\n    for (const reelSet of this._reels.values()) {\n      if (reelSet.isSpinning) {\n        inFlight += 1;\n        try {\n          reelSet.skipSpin();\n        } catch {\n          /* result not provided yet - nothing to skip to; ignore */\n        }\n      }\n    }\n    return inFlight;\n  }\n\n  get isDestroyed(): boolean {\n    return this._destroyed;\n  }\n\n  destroy(): void {\n    if (this._destroyed) return;\n    this._destroyed = true;\n    for (const reelSet of this._reels.values()) reelSet.destroy();\n    this._reels.clear();\n    this._cells.length = 0;\n    this.container.destroy({ children: true });\n  }\n\n  private _origin(cell: BoardCell): { x: number; y: number } {\n    return {\n      x: cell.reel * (this.cellSize + this.gap),\n      y: cell.cell * (this.cellSize + this.gap),\n    };\n  }\n\n  private _reel(cell: BoardCell): ReelSet {\n    const reelSet = this._reels.get(key(cell));\n    if (!reelSet) {\n      throw new Error(`BoardGrid: cell ${key(cell)} is outside the ${this.cols}x${this.rows} grid.`);\n    }\n    return reelSet;\n  }\n}\n","/**\n * Public types for the Hold & Win board. Kept in one leaf module so the\n * reducer ({@link HoldAndWinState}), the driver ({@link HoldAndWinBoard}) and\n * the builder can share them without import cycles.\n */\n\n/** Grid coordinate of a board cell. */\nexport interface HwCell {\n  reel: number;\n  cell: number;\n}\n\n/**\n * A coin somewhere on the board. `id` selects the registered symbol art;\n * `data` is an opaque game-layer payload the board never interprets.\n *\n * **Ownership contract:** `cell` and `id` belong to the board - treat them as\n * read-only; the board keys its ledger by cell and mutating them is undefined.\n * `data` is yours: mutate its contents freely (a doubler does `coin.data.value\n * *= 2`), that is the intended way to carry live game state on a locked coin.\n */\nexport interface HwCoin<TData = unknown> {\n  readonly cell: HwCell;\n  readonly id: string;\n  data?: TData;\n}\n\n/** Why the respin counter changed - disambiguates a `respins:changed` event. */\nexport type HwRespinReason = 'seed' | 'hit-reset' | 'miss';\n\n/** Resolution of one {@link HoldAndWinBoard.respin} wave. */\nexport interface HwRespinResult<TData = unknown> {\n  round: number;\n  hits: HwCoin<TData>[];\n  respinsLeft: number;\n  full: boolean;\n  /** True when the feature is over (counter exhausted or board full). */\n  done: boolean;\n}\n\nexport type HoldAndWinBoardEvents<TData = unknown> = {\n  'feature:enter': [{ seed: HwCoin<TData>[]; respins: number }];\n  'respin:start': [{ round: number; respinsLeft: number; spinning: HwCell[] }];\n  /** Fires per cell, in landing (stagger) order. `coin` is null on a miss. */\n  'cell:landed': [{ cell: HwCell; coin: HwCoin<TData> | null }];\n  'coin:locked': [{ coin: HwCoin<TData>; locked: number; capacity: number }];\n  /** Fired by `release()` - the collect / fly-away moment. */\n  'coin:released': [{ coin: HwCoin<TData>; remaining: number }];\n  'respins:changed': [{ value: number; reason: HwRespinReason }];\n  'respin:end': [{ round: number; hits: HwCoin<TData>[]; respinsLeft: number }];\n  'board:full': [{ coins: HwCoin<TData>[] }];\n  /** Fired by `skip()` so the game layer can cut its own flights / collect short. */\n  'feature:skip': [{ inFlight: number }];\n  /**\n   * Fired by `reset()` - a hard clear back to idle. Distinct from `coin:released`\n   * (which means \"collect this coin\"); listeners that maintain derived state\n   * from events (HUD totals, meters) clear it here without triggering collect.\n   */\n  'feature:reset': [{ clearedCoins: number }];\n  'feature:end': [{ coins: HwCoin<TData>[]; rounds: number; full: boolean }];\n};\n\n/**\n * One state-change the reducer ({@link HoldAndWinState}) decided, ready for the\n * driver to emit. A tagged pair of `{ type, payload }` for every board event the\n * reducer owns - the driver replays them onto its emitter and keys visual side\n * effects (e.g. `playWin()` on `coin:locked`) off the type. `respin:start` and\n * `feature:skip` are driver-owned (they describe in-flight reels, not ledger\n * state) and are not produced here.\n */\nexport type HwEffect<TData = unknown> = {\n  [K in keyof HoldAndWinBoardEvents<TData>]: {\n    type: K;\n    payload: HoldAndWinBoardEvents<TData>[K][0];\n  };\n}[keyof HoldAndWinBoardEvents<TData>];\n\nexport interface HwCellSizeOptions {\n  gap?: number;\n}\n\n/** `reel,cell` string key for the board's cell-indexed maps. */\nexport const cellKey = (c: HwCell): string => `${c.reel},${c.cell}`;\n","import type { HwCell, HwCoin, HwEffect, HwRespinReason } from './HwTypes.js';\nimport { cellKey } from './HwTypes.js';\n\nexport type HwPhase = 'idle' | 'active' | 'spinning';\n\n/**\n * The pure Hold & Win state machine - the single source of truth for board\n * state, with **zero** PixiJS. It owns the locked-coin ledger, the respin\n * counter, the round number and the feature {@link HwPhase}; every derived\n * value (`freeCells`, `isFull`) is computed from the one ledger, never stored\n * in parallel, so nothing can drift out of sync.\n *\n * It is a *reducer, not a cache*: {@link HoldAndWinBoard} drives the reels and\n * reports each landing here; the methods mutate the ledger and **return the\n * ordered list of {@link HwEffect}s to emit**, which the driver replays onto its\n * event emitter (and uses to fire visual side effects like `playWin()`). Locks\n * happen progressively as cells land in stagger order, so this is an\n * *incremental* reducer ({@link beginWave} → N×{@link land} → {@link endWave}),\n * not a one-shot `reduce(state, hits)`.\n *\n * Being PixiJS-free, it is fully unit-testable: assert the returned effect\n * sequence for hit / miss / full / release / reset / error paths.\n */\nexport class HoldAndWinState<TData = unknown> {\n  private readonly _locked = new Map<string, HwCoin<TData>>();\n  private readonly _cellSet: Set<string>;\n  private readonly _allCells: HwCell[];\n  private readonly _defaultRespins: number;\n  private _respinsLeft = 0;\n  private _round = 0;\n  private _phase: HwPhase = 'idle';\n  private _waveLanded: HwCoin<TData>[] = [];\n\n  constructor(allCells: HwCell[], defaultRespins: number) {\n    this._allCells = allCells;\n    this._cellSet = new Set(allCells.map(cellKey));\n    this._defaultRespins = defaultRespins;\n  }\n\n  // ── Queries ──────────────────────────────────────────────────────────\n\n  get phase(): HwPhase {\n    return this._phase;\n  }\n  get respinsLeft(): number {\n    return this._respinsLeft;\n  }\n  get round(): number {\n    return this._round;\n  }\n  get capacity(): number {\n    return this._allCells.length;\n  }\n  get isFull(): boolean {\n    return this._locked.size === this.capacity;\n  }\n\n  lockedCoins(): HwCoin<TData>[] {\n    return [...this._locked.values()];\n  }\n\n  freeCells(): HwCell[] {\n    return this._allCells.filter((c) => !this._locked.has(cellKey(c)));\n  }\n\n  isLocked(cell: HwCell): boolean {\n    return this._locked.has(cellKey(cell));\n  }\n\n  coinAt(cell: HwCell): HwCoin<TData> | undefined {\n    return this._locked.get(cellKey(cell));\n  }\n\n  // ── Transitions (mutate + return effects to emit) ───────────────────\n\n  /** Seed the trigger coins and arm the counter. Idle → active. */\n  enter(seed: HwCoin<TData>[]): HwEffect<TData>[] {\n    if (this._phase !== 'idle') {\n      throw new Error('HoldAndWinBoard: enter() while a feature is active — call reset() first.');\n    }\n    const placed: HwCoin<TData>[] = [];\n    const seen = new Set<string>();\n    for (const coin of seed) {\n      const k = cellKey(coin.cell);\n      this._assertInGrid(coin.cell, 'enter');\n      if (seen.has(k)) throw new Error(`HoldAndWinBoard: enter() seeds cell ${k} twice.`);\n      seen.add(k);\n      const stored = this._freeze(coin.cell, coin.id, coin.data);\n      this._locked.set(k, stored);\n      placed.push(stored);\n    }\n    this._round = 0;\n    this._phase = 'active';\n    return [\n      this._setRespins(this._defaultRespins, 'seed'),\n      { type: 'feature:enter', payload: { seed: placed, respins: this._respinsLeft } },\n    ];\n  }\n\n  /**\n   * Open a wave: validate the hits, mark every free cell as spinning, bump the\n   * round. Returns the data the driver needs to emit `respin:start` and to land\n   * each cell. Active → spinning.\n   */\n  beginWave(hits: HwCoin<TData>[]): {\n    round: number;\n    spinning: HwCell[];\n    hitByKey: Map<string, HwCoin<TData>>;\n  } {\n    if (this._phase === 'idle') {\n      throw new Error('HoldAndWinBoard: respin() before enter().');\n    }\n    if (this._phase === 'spinning') {\n      throw new Error('HoldAndWinBoard: respin() while a wave is in flight.');\n    }\n    const hitByKey = new Map<string, HwCoin<TData>>();\n    for (const hit of hits) {\n      const k = cellKey(hit.cell);\n      this._assertInGrid(hit.cell, 'respin');\n      if (this._locked.has(k)) throw new Error(`HoldAndWinBoard: hit targets locked cell ${k}.`);\n      // A free cell can only land once per wave, so a duplicate hit is always a\n      // malformed result. Fail loud rather than silently dropping the first coin.\n      if (hitByKey.has(k)) throw new Error(`HoldAndWinBoard: respin() targets cell ${k} twice.`);\n      hitByKey.set(k, hit);\n    }\n    this._waveLanded = [];\n    this._phase = 'spinning';\n    this._round += 1;\n    return { round: this._round, spinning: this.freeCells(), hitByKey };\n  }\n\n  /** Record one cell's landing. `coin` is null on a miss. */\n  land(cell: HwCell, coin: HwCoin<TData> | null): HwEffect<TData>[] {\n    // Outside a wave this is a stray landing - a sibling reel settling after\n    // the wave was aborted on error, or a cell landing after reset(). Drop it\n    // so it can't re-lock a coin into a cleared ledger or resurrect a feature.\n    if (this._phase !== 'spinning') return [];\n    if (!coin) {\n      return [{ type: 'cell:landed', payload: { cell, coin: null } }];\n    }\n    const stored = this._freeze(cell, coin.id, coin.data);\n    this._locked.set(cellKey(cell), stored);\n    this._waveLanded.push(stored);\n    return [\n      { type: 'cell:landed', payload: { cell, coin: stored } },\n      {\n        type: 'coin:locked',\n        payload: { coin: stored, locked: this._locked.size, capacity: this.capacity },\n      },\n    ];\n  }\n\n  /** Close the wave: resolve the counter, detect full / feature end. */\n  endWave(): { effects: HwEffect<TData>[]; landed: HwCoin<TData>[] } {\n    // The wave was aborted or reset out from under us (see land()'s guard).\n    // Closing it would re-arm the counter and flip a finished feature back to\n    // active off a wave that no longer exists. Do nothing.\n    if (this._phase !== 'spinning') return { effects: [], landed: [] };\n    const landed = this._waveLanded;\n    const effects: HwEffect<TData>[] = [];\n    effects.push(\n      landed.length > 0\n        ? this._setRespins(this._defaultRespins, 'hit-reset')\n        : this._setRespins(this._respinsLeft - 1, 'miss'),\n    );\n    this._phase = 'active';\n    effects.push({\n      type: 'respin:end',\n      payload: { round: this._round, hits: [...landed], respinsLeft: this._respinsLeft },\n    });\n    const full = this.isFull;\n    if (full) effects.push({ type: 'board:full', payload: { coins: this.lockedCoins() } });\n    const done = full || this._respinsLeft <= 0;\n    if (done) {\n      this._phase = 'idle';\n      effects.push({\n        type: 'feature:end',\n        payload: { coins: this.lockedCoins(), rounds: this._round, full },\n      });\n    }\n    // Hand back a caller-owned copy, not the live `_waveLanded` reference, so a\n    // consumer that mutates `respin().hits` can't reach into reducer state. The\n    // `respin:end` event payload above is already copied for the same reason.\n    return { effects, landed: [...landed] };\n  }\n\n  /**\n   * Abandon an in-flight wave after a driver error: restore the phase from\n   * `spinning` back to `active` so a thrown spin doesn't strand the board (every\n   * later `beginWave` would otherwise throw \"wave in flight\"). Cells that already\n   * landed stay locked; the caller decides whether to retry or `reset`.\n   */\n  abortWave(): void {\n    if (this._phase !== 'spinning') return;\n    this._phase = 'active';\n    this._waveLanded = [];\n  }\n\n  /** Remove locked coins - the collect moment. */\n  release(cells: HwCell[]): { effects: HwEffect<TData>[]; released: HwCoin<TData>[] } {\n    if (this._phase === 'spinning') {\n      throw new Error('HoldAndWinBoard: release() while a wave is in flight — await respin() first.');\n    }\n    const effects: HwEffect<TData>[] = [];\n    const released: HwCoin<TData>[] = [];\n    for (const cell of cells) {\n      const k = cellKey(cell);\n      const coin = this._locked.get(k);\n      if (!coin) continue;\n      this._locked.delete(k);\n      released.push(coin);\n      effects.push({ type: 'coin:released', payload: { coin, remaining: this._locked.size } });\n    }\n    return { effects, released };\n  }\n\n  /**\n   * Rewrite a **locked** cell's coin identity in place (coin → jackpot, mini →\n   * major). Throws on a free cell - placing a brand-new tracked coin out of a\n   * spin is `enter`/`respin`'s job; for purely decorative art on a free cell use\n   * the cell's reel directly.\n   */\n  swap(cell: HwCell, id: string, data: TData | undefined): void {\n    if (this._phase === 'spinning') {\n      throw new Error('HoldAndWinBoard: setSymbolAt() while a wave is in flight — await respin() first.');\n    }\n    const k = cellKey(cell);\n    const prev = this._locked.get(k);\n    if (!prev) {\n      throw new Error(\n        `HoldAndWinBoard: setSymbolAt(${k}) on a non-locked cell — setSymbolAt rewrites a locked coin's identity.`,\n      );\n    }\n    this._locked.set(k, this._freeze(cell, id, data ?? prev.data));\n  }\n\n  /** Hard clear back to idle. Fires `feature:reset`, never `coin:released`. */\n  reset(): HwEffect<TData>[] {\n    const clearedCoins = this._locked.size;\n    this._locked.clear();\n    this._waveLanded = [];\n    this._round = 0;\n    this._respinsLeft = 0;\n    this._phase = 'idle';\n    return [{ type: 'feature:reset', payload: { clearedCoins } }];\n  }\n\n  // ── Internals ────────────────────────────────────────────────────────\n\n  private _setRespins(value: number, reason: HwRespinReason): HwEffect<TData> {\n    this._respinsLeft = Math.max(0, value);\n    return { type: 'respins:changed', payload: { value: this._respinsLeft, reason } };\n  }\n\n  /**\n   * Store a coin with a board-owned, frozen `cell` so the ledger key can never\n   * be corrupted by a game mutating the coin it was handed. `data` is left\n   * mutable by reference - that is the supported way to carry live value.\n   */\n  private _freeze(cell: HwCell, id: string, data: TData | undefined): HwCoin<TData> {\n    return { cell: Object.freeze({ reel: cell.reel, cell: cell.cell }), id, data };\n  }\n\n  private _assertInGrid(cell: HwCell, op: string): void {\n    if (!this._cellSet.has(cellKey(cell))) {\n      throw new Error(`HoldAndWinBoard: ${op}() targets cell ${cellKey(cell)} outside the grid.`);\n    }\n  }\n}\n","import type { Container, Graphics, Ticker } from 'pixi.js';\nimport { EventEmitter } from '../events/EventEmitter.js';\nimport type { ReelSet } from '../core/ReelSet.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport type { SpeedProfile, SymbolData } from '../config/types.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport { BoardGrid } from './BoardGrid.js';\nimport type { Direction, Orientation } from '../core/ReelAxis.js';\nimport { HoldAndWinState } from './HoldAndWinState.js';\nimport type { HwPhase } from './HoldAndWinState.js';\nimport { cellKey } from './HwTypes.js';\nimport type {\n  HoldAndWinBoardEvents,\n  HwCell,\n  HwCoin,\n  HwEffect,\n  HwRespinResult,\n} from './HwTypes.js';\n\n/** Internal config produced by {@link HoldAndWinBuilder.build}. */\nexport interface HoldAndWinBoardConfig<TData> {\n  cols: number;\n  rows: number;\n  cell: number;\n  gap: number;\n  emptyId: string;\n  respins: number;\n  configurator: (registry: SymbolRegistry) => void;\n  weights: Record<string, number> | null;\n  symbolData: Record<string, Partial<SymbolData>> | null;\n  baseProfile: SpeedProfile;\n  stagger: (reel: number, cell: number) => number;\n  anticipateWhen:\n    | ((state: { locked: number; capacity: number; respinsLeft: number }) => boolean)\n    | null;\n  chrome: ((g: Graphics, size: number) => void) | null;\n  /** Travel axis for each cell's own strip. See `HoldAndWinBuilder.axis`. */\n  orientation?: Orientation;\n  direction?: Direction;\n  ticker: Ticker;\n  rng: (() => number) | null;\n}\n\n/** Extra spin time (ms) a tense wave adds on top of the normal profile. */\nconst TENSION_EXTRA_MS = 1100;\n\n/**\n * A Hold & Win board: a grid of independently spinning cells plus the round\n * choreography every H&W game repeats - spin the free cells, lock the hits,\n * reset-or-decrement the respin counter, detect the full board.\n *\n * It composes two collaborators: a `BoardGrid` (the generic \"board of reels\"\n * mechanism - geometry, instances, spinning) and a `HoldAndWinState` (the pure\n * single-source reducer - ledger, counter, phase). The board is the\n * mediator: it drives the reels, reports each landing to the reducer, and\n * replays the reducer's decided effects onto {@link events}.\n *\n * It deliberately owns nothing about *value*. Coins are opaque `{ cell, id, data }`\n * - `id` picks the registered art, `data` is the game layer's to read and mutate.\n * Adders, doublers, collectors and flights are game design, expressed through\n * three openings rather than board features: {@link events}, {@link symbolAt}\n * (the live `ReelSymbol` instance) and {@link cellBounds}/{@link cellCenter}\n * (pixel geometry for flights).\n *\n * ```ts\n * const board = new HoldAndWinBuilder<{ value: number }>()\n *   .grid(5, 3).cellSize(72, { gap: 4 })\n *   .symbols((r) => r.register('coin', CoinSymbol, COIN_TRIGGER))\n *   .weights({ coin: 1, empty: 3 }).respins(3).ticker(app.ticker)\n *   .build();\n *\n * board.events.on('coin:locked', ({ coin }) => hud.add(coin.data.value));\n * board.enter(triggerCoins);\n * while (true) {\n *   const round = await server.respin(board.lockedCoins);\n *   const result = await board.respin(round.hits);\n *   if (result.done) break;                  // game animates between rounds\n * }\n * ```\n */\nexport class HoldAndWinBoard<TData = unknown> implements Disposable {\n  readonly events = new EventEmitter<HoldAndWinBoardEvents<TData>>();\n  readonly cols: number;\n  readonly rows: number;\n\n  private readonly _grid: BoardGrid;\n  private readonly _state: HoldAndWinState<TData>;\n  private readonly _emptyId: string;\n  private readonly _anticipateWhen: HoldAndWinBoardConfig<TData>['anticipateWhen'];\n\n  constructor(cfg: HoldAndWinBoardConfig<TData>) {\n    this.cols = cfg.cols;\n    this.rows = cfg.rows;\n    this._emptyId = cfg.emptyId;\n    this._anticipateWhen = cfg.anticipateWhen;\n\n    const base = (cell: HwCell): number =>\n      (cfg.baseProfile.minimumSpinTime ?? 320) + cfg.stagger(cell.reel, cell.cell);\n    this._grid = new BoardGrid({\n      cols: cfg.cols,\n      rows: cfg.rows,\n      cellSize: cfg.cell,\n      gap: cfg.gap,\n      emptyId: cfg.emptyId,\n      symbols: cfg.configurator,\n      weights: cfg.weights ?? undefined,\n      symbolData: cfg.symbolData ?? undefined,\n      chrome: cfg.chrome ?? undefined,\n      orientation: cfg.orientation,\n      direction: cfg.direction,\n      ticker: cfg.ticker,\n      rng: cfg.rng ?? undefined,\n      profiles: {\n        normal: (cell) => ({ ...cfg.baseProfile, minimumSpinTime: base(cell) }),\n        tension: (cell) => ({ ...cfg.baseProfile, minimumSpinTime: base(cell) + TENSION_EXTRA_MS }),\n      },\n    });\n    this._state = new HoldAndWinState<TData>(this._grid.cells(), cfg.respins);\n  }\n\n  // ── State (delegated to the single-source reducer) ───────────────────\n\n  get container(): Container {\n    return this._grid.container;\n  }\n  get capacity(): number {\n    return this._state.capacity;\n  }\n  get respinsLeft(): number {\n    return this._state.respinsLeft;\n  }\n  get lockedCoins(): HwCoin<TData>[] {\n    return this._state.lockedCoins();\n  }\n  get isFull(): boolean {\n    return this._state.isFull;\n  }\n  get freeCells(): HwCell[] {\n    return this._state.freeCells();\n  }\n  /** Where the feature is right now: idle (no feature), active, or spinning. */\n  get phase(): HwPhase {\n    return this._state.phase;\n  }\n\n  // ── Geometry & instances (the game layer's openings) ────────────────\n\n  cellBounds(cell: HwCell): { x: number; y: number; width: number; height: number } {\n    return this._grid.cellBounds(cell);\n  }\n  cellCenter(cell: HwCell): { x: number; y: number } {\n    return this._grid.cellCenter(cell);\n  }\n  /** Live symbol instance currently shown in a cell. */\n  symbolAt(cell: HwCell): ReelSymbol {\n    return this._grid.symbolAt(cell);\n  }\n  /** The cell's underlying 1×1 ReelSet, for driving one cell directly. */\n  reelAt(cell: HwCell): ReelSet {\n    return this._grid.reelAt(cell);\n  }\n\n  /**\n   * Rewrite a **locked** cell's coin in place - coin → jackpot, mini → major,\n   * raise a tier - without disturbing any other cell. The ledger entry is\n   * rewritten so `lockedCoins` and totals stay correct. Throws on a free cell.\n   * Returns the new live symbol instance.\n   *\n   * Throws if called while a wave is in flight - `await respin()` first. To\n   * upgrade a coin in reaction to its own `coin:locked`, defer the swap until\n   * the awaited `respin()` resolves rather than swapping inside the listener.\n   */\n  setSymbolAt(cell: HwCell, id: string, data?: TData): ReelSymbol {\n    this._state.swap(cell, id, data);\n    this._grid.place(cell, id);\n    return this._grid.symbolAt(cell);\n  }\n\n  // ── Round choreography ───────────────────────────────────────────────\n\n  /** Activate the feature with the trigger coins. Seeds land locked, instantly. */\n  enter(seed: HwCoin<TData>[]): void {\n    const effects = this._state.enter(seed); // validates first; throws before any visual\n    for (const coin of seed) this._grid.place(coin.cell, coin.id);\n    this._apply(effects);\n  }\n\n  /**\n   * Spin every free cell; `hits` land (and lock) their coins, all other spinning\n   * cells land empty. Resolves once the wave has landed and the counter is\n   * resolved. The game layer drives pacing between rounds.\n   */\n  async respin(hits: HwCoin<TData>[]): Promise<HwRespinResult<TData>> {\n    const { round, spinning, hitByKey } = this._state.beginWave(hits);\n    try {\n      const tense = this._anticipating() && spinning.length > 0;\n      for (const cell of spinning) this._grid.setProfile(cell, tense ? 'tension' : 'normal');\n      this.events.emit('respin:start', { round, respinsLeft: this._state.respinsLeft, spinning });\n\n      const targets = spinning.map((cell) => ({\n        cell,\n        id: hitByKey.get(cellKey(cell))?.id ?? this._emptyId,\n      }));\n      await this._grid.spinCells(targets, (cell) => {\n        this._apply(this._state.land(cell, hitByKey.get(cellKey(cell)) ?? null));\n      });\n\n      const { effects, landed } = this._state.endWave();\n      this._apply(effects);\n      return {\n        round,\n        hits: landed,\n        respinsLeft: this._state.respinsLeft,\n        full: this._state.isFull,\n        done: this._state.phase === 'idle',\n      };\n    } catch (err) {\n      // A synchronous throw between beginWave and endWave - most plausibly a\n      // game-layer event listener (respin:start / cell:landed / coin:locked)\n      // throwing - must not strand the board. (An unregistered symbol id does\n      // NOT land here: the engine logs and slams the reel internally, so the\n      // spin resolves rather than rejecting.) abortWave() restores the reducer\n      // phase, else every later respin() throws \"wave in flight\"; skipSpinning()\n      // lands any cell still in flight, else the next respin() throws \"already\n      // spinning\" on it. Stray landings from those slams are dropped by the\n      // reducer's not-spinning guard. Rethrow so the caller still sees it.\n      this._state.abortWave();\n      this._grid.skipSpinning();\n      throw err;\n    }\n  }\n\n  /**\n   * Remove locked coins - the collect moment. Clears the cells (they become\n   * free again) and returns the released coins; the flight itself is game-layer\n   * animation, started from `cellCenter()` or the `coin:released` event.\n   */\n  release(cells: HwCell[]): HwCoin<TData>[] {\n    const { effects, released } = this._state.release(cells);\n    for (const coin of released) this._grid.place(coin.cell, this._emptyId);\n    this._apply(effects);\n    return released;\n  }\n\n  /**\n   * Fast-forward whatever is spinning: every in-flight cell is slammed to its\n   * landed position, then `feature:skip` fires so the game layer can cut its own\n   * flights short. The normal landing → `coin:locked` → `feature:end` flow still\n   * resolves; this only removes the waiting. Returns the number of cells that\n   * were in flight.\n   */\n  skip(): number {\n    const inFlight = this._grid.skipSpinning();\n    this.events.emit('feature:skip', { inFlight });\n    return inFlight;\n  }\n\n  /** Clear the board back to idle. Fires `feature:reset` (not `coin:released`). */\n  reset(): void {\n    const effects = this._state.reset();\n    for (const cell of this._grid.cells()) this._grid.place(cell, this._emptyId);\n    this._apply(effects);\n  }\n\n  get isDestroyed(): boolean {\n    return this._grid.isDestroyed;\n  }\n\n  destroy(): void {\n    if (this._grid.isDestroyed) return;\n    this.events.removeAllListeners();\n    this._grid.destroy();\n  }\n\n  // ── Internals ────────────────────────────────────────────────────────\n\n  /** Emit each reducer-decided effect and fire the visual side effects. */\n  private _apply(effects: HwEffect<TData>[]): void {\n    for (const fx of effects) {\n      // Correlated union: `fx.type` and `fx.payload` are paired by construction\n      // in the reducer, but TS can't carry that correlation through `emit`'s\n      // generic. One local cast keeps every other call site fully typed.\n      (this.events.emit as (type: string, payload: unknown) => void)(fx.type, fx.payload);\n      if (fx.type === 'coin:locked') {\n        // playWin is presentation; a hiccup must not break the feature flow, but\n        // it must not vanish silently either - log it like the rest of the engine.\n        void this.symbolAt(fx.payload.coin.cell)\n          .playWin()\n          .catch((err) => console.warn('HoldAndWinBoard: coin win animation failed.', err));\n      }\n    }\n  }\n\n  private _anticipating(): boolean {\n    if (!this._anticipateWhen) return false;\n    return this._anticipateWhen({\n      locked: this._state.lockedCoins().length,\n      capacity: this._state.capacity,\n      respinsLeft: this._state.respinsLeft,\n    });\n  }\n}\n","import type { Graphics, Ticker } from 'pixi.js';\nimport { SpeedPresets } from '../config/SpeedPresets.js';\nimport type { SpeedProfile, SymbolData } from '../config/types.js';\nimport type { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport { HoldAndWinBoard } from './HoldAndWinBoard.js';\nimport type { Direction, Orientation } from '../core/ReelAxis.js';\nimport type { HwCellSizeOptions } from './HwTypes.js';\n\n/**\n * Fluent builder for {@link HoldAndWinBoard}.\n *\n * A Hold & Win board is a W×H grid of cells that spin **independently** - the\n * mechanic's atomic unit is the cell, the engine's is the column, so each cell\n * is its own 1×1 ReelSet. This builder wires that grid plus the round\n * choreography; everything value-shaped stays in the game layer (see\n * {@link HoldAndWinBoard}).\n *\n * `TData` types the opaque payload carried on each coin's `data`.\n */\nexport class HoldAndWinBuilder<TData = unknown> {\n  private _cols = 5;\n  private _rows = 3;\n  private _cell = 72;\n  private _gap = 4;\n  private _emptyId = 'empty';\n  private _respins = 3;\n  private _configurator: ((registry: SymbolRegistry) => void) | null = null;\n  private _weights: Record<string, number> | null = null;\n  private _symbolData: Record<string, Partial<SymbolData>> | null = null;\n  private _baseProfile: SpeedProfile = { ...SpeedPresets.NORMAL, minimumSpinTime: 320 };\n  private _stagger: (reel: number, cell: number) => number = (reel, cell) => (reel + cell) * 70;\n  private _anticipateWhen:\n    | ((state: { locked: number; capacity: number; respinsLeft: number }) => boolean)\n    | null = null;\n  private _chrome: ((g: Graphics, size: number) => void) | null = null;\n  private _orientation: Orientation = 'vertical';\n  private _direction: Direction = 'forward';\n  private _ticker: Ticker | null = null;\n  private _rng: (() => number) | null = null;\n\n  grid(cols: number, rows: number): this {\n    this._cols = cols;\n    this._rows = rows;\n    return this;\n  }\n\n  cellSize(size: number, opts: HwCellSizeOptions = {}): this {\n    this._cell = size;\n    this._gap = opts.gap ?? this._gap;\n    return this;\n  }\n\n  /**\n   * Register coin symbol classes, exactly like `ReelSetBuilder.symbols`. Applied\n   * to every cell. An {@link EmptySymbol} is auto-registered under {@link emptyId}\n   * unless the configurator registers one itself.\n   */\n  symbols(configurator: (registry: SymbolRegistry) => void): this {\n    this._configurator = configurator;\n    return this;\n  }\n\n  /** Strip weights during the spin (how often coins flash past empties). */\n  weights(weights: Record<string, number>): this {\n    this._weights = weights;\n    return this;\n  }\n\n  /** Symbol id a cell shows when it holds no coin. Default `'empty'`. */\n  emptyId(id: string): this {\n    this._emptyId = id;\n    return this;\n  }\n\n  /**\n   * Per-symbol engine overrides, exactly like `ReelSetBuilder.symbolData`. The\n   * headline use is `{ unmask: true }` for coins whose lock/reveal animations\n   * expand past the cell. Safe only for server-placed ids (weight 0): unmasked\n   * strip symbols mis-track vertically while the reel spins.\n   */\n  symbolData(overrides: Record<string, Partial<SymbolData>>): this {\n    this._symbolData = { ...(this._symbolData ?? {}), ...overrides };\n    return this;\n  }\n\n  /** Respins granted on enter and restored on every hit. Default 3. */\n  respins(count: number): this {\n    this._respins = count;\n    return this;\n  }\n\n  /** Base spin feel for every cell. Default: NORMAL with a 320ms floor. */\n  speedProfile(profile: SpeedProfile): this {\n    this._baseProfile = profile;\n    return this;\n  }\n\n  /**\n   * Extra milliseconds of spin per cell on top of the base minimum spin time.\n   * Default `(reel + cell) * 70` - the diagonal landing wave. Return 0 for\n   * simultaneous landings.\n   */\n  stagger(fn: (reel: number, cell: number) => number): this {\n    this._stagger = fn;\n    return this;\n  }\n\n  /**\n   * When the predicate returns true for a wave, **every** spinning cell uses a\n   * drawn-out tension profile - the \"one cell left for Grand\" moment. Evaluated\n   * once per wave for the whole board (not per cell), against the pre-wave state.\n   */\n  anticipateWhen(\n    fn: (state: { locked: number; capacity: number; respinsLeft: number }) => boolean,\n  ): this {\n    this._anticipateWhen = fn;\n    return this;\n  }\n\n  /** Per-cell background, drawn behind each mini reel. */\n  cellChrome(draw: (g: Graphics, size: number) => void): this {\n    this._chrome = draw;\n    return this;\n  }\n\n  /**\n   * Which way each cell's strip travels while it spins. Cells are 1x1 reel\n   * sets, so this picks the edge a coin scrolls in from; the board's own\n   * `cols` x `rows` layout is unaffected. Defaults to vertical / forward.\n   */\n  axis(orientation: Orientation, direction: Direction = 'forward'): this {\n    this._orientation = orientation;\n    this._direction = direction;\n    return this;\n  }\n\n  ticker(ticker: Ticker): this {\n    this._ticker = ticker;\n    return this;\n  }\n\n  /** Injected RNG for the spin strips (deterministic demos / tests). */\n  rng(fn: () => number): this {\n    this._rng = fn;\n    return this;\n  }\n\n  build(): HoldAndWinBoard<TData> {\n    if (!this._configurator) {\n      throw new Error('HoldAndWinBuilder: .symbols(...) is required — register at least one coin id.');\n    }\n    if (!this._ticker) {\n      throw new Error('HoldAndWinBuilder: .ticker(...) is required.');\n    }\n    return new HoldAndWinBoard<TData>({\n      cols: this._cols,\n      rows: this._rows,\n      cell: this._cell,\n      gap: this._gap,\n      emptyId: this._emptyId,\n      respins: this._respins,\n      configurator: this._configurator,\n      weights: this._weights,\n      symbolData: this._symbolData,\n      baseProfile: this._baseProfile,\n      stagger: this._stagger,\n      anticipateWhen: this._anticipateWhen,\n      chrome: this._chrome,\n      orientation: this._orientation,\n      direction: this._direction,\n      ticker: this._ticker,\n      rng: this._rng,\n    });\n  }\n}\n","import type { SymbolPosition, Win } from '../config/types.js';\n\nexport type { Win, SymbolPosition };\n\n/** Return a new array sorted by `value` descending (non-mutating). Missing values sort as 0. */\nexport function sortByValueDesc<T extends { value?: number }>(wins: readonly T[]): T[] {\n  return [...wins].sort((a, b) => (b.value ?? 0) - (a.value ?? 0));\n}\n","import type { Win } from '../config/types.js';\nimport type { ReelSet } from '../core/ReelSet.js';\nimport type { SymbolPosition } from '../events/ReelEvents.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport { sortByValueDesc } from './Win.js';\n\n/**\n * What to play on each cell being highlighted.\n *\n *   - `'win'`. default. Calls `symbol.playWin()` (your subclass's hook).\n *   - `string`. a named animation. If the symbol exposes a\n *     `playAnimation(name)` method (e.g. SpineSymbol), it's invoked; else\n *     it falls back to `playWin()`.\n *   - `(symbol, cell, win) => Promise<void>`. drive the animation\n *     yourself. Good for GSAP bounces, line-specific pulses, etc.\n */\nexport type WinSymbolAnim =\n  | 'win'\n  | string\n  | ((symbol: ReelSymbol, cell: SymbolPosition, win: Win) => Promise<void>);\n\nexport interface WinPresenterOptions {\n  /**\n   * Fade non-winning symbols to this alpha while a win is active.\n   *  - `true` (default) → alpha 0.35\n   *  - number → that alpha\n   *  - `false` → don't touch non-winners\n   *\n   * Restored on `win:end`.\n   */\n  dimLosers?: boolean | { alpha?: number };\n  /** See {@link WinSymbolAnim}. Default `'win'`. */\n  symbolAnim?: WinSymbolAnim;\n  /**\n   * Delay between cells *within* a single win (ms).\n   * - `0` (default) → all cells animate simultaneously\n   * - `> 0` → cells start one after another in array order (e.g. a\n   *   left-to-right sweep across a payline's cells)\n   */\n  stagger?: number;\n  /** Delay between successive wins in the sequence (ms). Default 400. */\n  cycleGap?: number;\n  /** Number of full cycles through the wins list. `-1` for infinite. Default 1. */\n  cycles?: number;\n  /** Sort wins by `value` descending before cycling. Default true. */\n  sortByValue?: boolean;\n}\n\ninterface ResolvedOptions {\n  dimAlpha: number | null;\n  symbolAnim: WinSymbolAnim;\n  stagger: number;\n  cycleGap: number;\n  cycles: number;\n  sortByValue: boolean;\n}\n\n/**\n * Highlights winning cells on a reel set. One job: animate the symbols.\n *\n * The presenter doesn't draw lines, outlines, or any per-win visual. it\n * emits `win:start` / `win:group` / `win:symbol` / `win:end` events so\n * your code can hook anything it wants (polylines, Spine line rigs,\n * popup numbers, sound cues) by subscribing and using\n * `reelSet.getCellBounds(reel, cell)` to place graphics.\n *\n * Two knobs cover the common presentation modes:\n *\n *   - `stagger: 0` → all cells in a win pulse together\n *   - `stagger: 60` → cells start one after another. a left-to-right\n *     sweep if you pass cells in reel order\n *\n * ```ts\n * const presenter = new WinPresenter(reelSet, { stagger: 80 });\n *\n * reelSet.events.on('spin:complete', async () => {\n *   const wins = await server.wins(result);  // your wins, your shape\n *   await presenter.show(wins);\n * });\n * reelSet.events.on('spin:start', () => presenter.abort());\n * ```\n *\n * Cascades: drive `presenter.show([{ cells: winners }])` from\n * `runCascade`'s `onWinnersVanish` hook. cluster pops and payline hits\n * are the same shape to the presenter.\n */\nexport class WinPresenter implements Disposable {\n  private _reelSet: ReelSet;\n  private _options: ResolvedOptions;\n  private _abort: AbortController | null = null;\n  private _isActive = false;\n  private _isDestroyed = false;\n\n  constructor(reelSet: ReelSet, options: WinPresenterOptions = {}) {\n    this._reelSet = reelSet;\n    this._options = WinPresenter._resolve(options);\n  }\n\n  get isActive(): boolean {\n    return this._isActive;\n  }\n\n  get isDestroyed(): boolean {\n    return this._isDestroyed;\n  }\n\n  /**\n   * Present the given wins. Cancels any in-flight sequence first.\n   * Resolves when all cycles complete or when `abort()` is called.\n   *\n   * Empty input resolves immediately without firing any events.\n   */\n  async show(wins: readonly Win[]): Promise<void> {\n    this.abort();\n    if (this._isDestroyed) return;\n    if (wins.length === 0) return;\n\n    const ordered = this._options.sortByValue\n      ? sortByValueDesc(wins)\n      : [...wins];\n\n    const abort = new AbortController();\n    this._abort = abort;\n    this._isActive = true;\n    this._reelSet.events.emit('win:start', ordered);\n\n    let loop = 0;\n    try {\n      while (this._options.cycles === -1 || loop < this._options.cycles) {\n        for (const win of ordered) {\n          if (abort.signal.aborted) return;\n          await this._showOne(win, abort.signal);\n          if (abort.signal.aborted) return;\n          await this._wait(this._options.cycleGap, abort.signal);\n        }\n        loop++;\n      }\n    } finally {\n      this._restoreAlpha();\n      const wasAborted = abort.signal.aborted;\n      if (this._abort === abort) this._abort = null;\n      this._isActive = false;\n      this._reelSet.events.emit('win:end', wasAborted ? 'aborted' : 'complete');\n    }\n  }\n\n  /** Abort any in-flight `show()`. */\n  abort(): void {\n    if (this._abort) this._abort.abort();\n  }\n\n  destroy(): void {\n    if (this._isDestroyed) return;\n    this._isDestroyed = true;\n    this.abort();\n  }\n\n  private async _showOne(win: Win, signal: AbortSignal): Promise<void> {\n    const cells = [...win.cells];\n    if (cells.length === 0) return;\n\n    // Apply dim before firing win:group so listeners observe the live\n    // visual state (e.g. a UI snapshot sees losers already faded).\n    this._applyDim(cells);\n    this._reelSet.events.emit('win:group', win, cells);\n\n    const stagger = this._options.stagger;\n    const animPromises: Promise<void>[] = [];\n\n    for (let i = 0; i < cells.length; i++) {\n      if (signal.aborted) break;\n      if (i > 0 && stagger > 0) await this._wait(stagger, signal);\n      if (signal.aborted) break;\n      const cell = cells[i];\n      const reel = this._reelSet.getReel(cell.reelIndex);\n      if (!reel) continue;\n      const symbol = reel.getSymbolAt(cell.cellIndex);\n      if (!symbol) continue;\n      this._reelSet.events.emit('win:symbol', symbol, cell, win);\n      animPromises.push(this._playAnim(symbol, cell, win));\n    }\n\n    await Promise.all(animPromises);\n  }\n\n  private async _playAnim(\n    symbol: ReelSymbol,\n    cell: SymbolPosition,\n    win: Win,\n  ): Promise<void> {\n    const anim = this._options.symbolAnim;\n    if (typeof anim === 'function') return anim(symbol, cell, win);\n    if (anim === 'win') return symbol.playWin();\n    const withPlay = symbol as unknown as { playAnimation?: (name: string) => Promise<void> };\n    if (typeof withPlay.playAnimation === 'function') return withPlay.playAnimation(anim);\n    return symbol.playWin();\n  }\n\n  private _applyDim(winCells: readonly SymbolPosition[]): void {\n    const alpha = this._options.dimAlpha;\n    if (alpha === null) return;\n\n    const winKeys = new Set<string>();\n    for (const c of winCells) winKeys.add(`${c.reelIndex}:${c.cellIndex}`);\n\n    const reels = this._reelSet.reels;\n    for (let r = 0; r < reels.length; r++) {\n      const reel = reels[r];\n      for (let cell = 0; cell < reel.visibleCells; cell++) {\n        const view = reel.getSymbolAt(cell).view;\n        view.alpha = winKeys.has(`${r}:${cell}`) ? 1 : alpha;\n      }\n    }\n  }\n\n  private _restoreAlpha(): void {\n    const reels = this._reelSet.reels;\n    for (const reel of reels) {\n      for (let cell = 0; cell < reel.visibleCells; cell++) {\n        reel.getSymbolAt(cell).view.alpha = 1;\n      }\n    }\n  }\n\n  private _wait(ms: number, signal: AbortSignal): Promise<void> {\n    return new Promise((resolve) => {\n      if (signal.aborted) return resolve();\n      const t = setTimeout(resolve, ms);\n      signal.addEventListener(\n        'abort',\n        () => {\n          clearTimeout(t);\n          resolve();\n        },\n        { once: true },\n      );\n    });\n  }\n\n  private static _resolve(opts: WinPresenterOptions): ResolvedOptions {\n    let dimAlpha: number | null;\n    if (opts.dimLosers === false) {\n      dimAlpha = null;\n    } else if (typeof opts.dimLosers === 'object' && opts.dimLosers !== null) {\n      dimAlpha = opts.dimLosers.alpha ?? 0.35;\n    } else {\n      dimAlpha = 0.35;\n    }\n\n    return {\n      dimAlpha,\n      symbolAnim: opts.symbolAnim ?? 'win',\n      stagger: Math.max(0, opts.stagger ?? 0),\n      cycleGap: opts.cycleGap ?? 400,\n      cycles: opts.cycles ?? 1,\n      sortByValue: opts.sortByValue ?? true,\n    };\n  }\n}\n","import type { Ticker } from 'pixi.js';\nimport { DEFAULT_GSAP, type Gsap } from './gsap.js';\n\n/**\n * Drive GSAP from a PixiJS ticker instead of its own requestAnimationFrame\n * loop, and return a disposer that restores GSAP's default loop.\n *\n * **Why this matters:** GSAP's default ticker runs on `requestAnimationFrame`,\n * which browsers throttle — and often freeze entirely — in hidden tabs and many\n * iframes. Casino lobbies and iframed clients are routinely backgrounded. When\n * that happens GSAP stalls while the PixiJS ticker keeps running, so every\n * engine animation (spin easing, the landing bounce, spotlight pulses) freezes\n * mid-flight. Pinning GSAP to the same ticker keeps animation and rendering in\n * lockstep everywhere, foreground or background.\n *\n * This is the one line every integration has to remember\n * (`gsap.ticker.remove(gsap.updateRoot); ticker.add(...)`); calling this\n * function instead removes that footgun.\n *\n * Pass the SAME instance you gave `ReelSetBuilder.gsap(...)`. gsap is held per\n * reel set in v2, not process-wide, so this function cannot look it up for you\n * -- and driving a different instance than the engine's tweens live on is the\n * dual-instance trap all over again. Omit it only if you never called\n * `.gsap(...)` either.\n *\n * Call it **once per app**, after creating the PixiJS `Application` and before\n * the first spin. Do NOT also detach `gsap.updateRoot` yourself — driving GSAP\n * from two sources advances it twice per frame (animations run at double speed).\n *\n * @param ticker - The PixiJS ticker to drive GSAP from (usually `app.ticker`).\n * @param instance - The gsap instance to drive. Defaults to the one resolved\n *   at lib-load time; pass the one you handed `ReelSetBuilder.gsap(...)`.\n * @returns A disposer that detaches the driver and restores GSAP's own ticker.\n *\n * @example\n * import { Application } from 'pixi.js';\n * import { driveGsapWithTicker } from 'pixi-reels';\n *\n * const app = new Application();\n * await app.init({ ... });\n * const stopGsapSync = driveGsapWithTicker(app.ticker);\n * // ...on teardown:\n * stopGsapSync();\n */\nexport function driveGsapWithTicker(ticker: Ticker, instance: Gsap = DEFAULT_GSAP): () => void {\n  const gsap = instance;\n  gsap.ticker.remove(gsap.updateRoot);\n  const driver = (): void => {\n    gsap.updateRoot(ticker.lastTime / 1000);\n  };\n  ticker.add(driver);\n\n  let disposed = false;\n  return () => {\n    if (disposed) return;\n    disposed = true;\n    ticker.remove(driver);\n    gsap.ticker.add(gsap.updateRoot);\n  };\n}\n"],"mappings":"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"}