import { Container } from 'pixi.js'; import { EventEmitter } from '../events/EventEmitter.js'; import { ReelSymbol } from '../symbols/ReelSymbol.js'; import { Disposable } from '../utils/Disposable.js'; import { ColumnTarget } from '../frame/ColumnTarget.js'; import { SpinResult } from '../events/ReelEvents.js'; import { HorizontalDirection, HorizontalReelConfig, HorizontalReelEvents } from './HorizontalReelTypes.js'; /** * A single horizontal reel — the banner reel that sits **above** the reels * announcing which symbols pay this round. * * It is one row, oriented sideways, and it follows the same contract as * {@link ReelSet}: * * - `spin()` starts it and returns a promise. * - `setResult(ids)` hands it the round's paying symbols (one per visible * cell) and triggers the stop; the `spin()` promise resolves on land. * - `cascade(winners, newIds?)` is the real tumble: the winning cells are * **removed**, the survivors **collapse** to close the gaps, and new symbols * **slide in from the feed edge**, exactly like the main reels' cascade. * Pass every cell for a full "they all drop", or just the winning cells. * * The engine's {@link Reel} wraps on the Y axis and bakes that in throughout, so * this is its own small mechanism on the shared primitives (the * {@link SymbolFactory} pool, {@link TickerRef}, the typed {@link EventEmitter}) * rather than a rotated reel. * * **Known trade-off / future direction.** This class deliberately *mirrors* the * {@link ReelSet} API rather than being one. That mirror is technical debt: it * re-implements the spin lifecycle and a parallel `cascade` next to * `ReelSet.runCascade`/`refill`, and it does not inherit anticipation, speed * modes, holds, pins, the spotlight or debug. The intended fix is to give * {@link Reel}/`ReelMotion`/`ReelViewport` an **orientation axis** so ONE reel * does vertical or horizontal, and this class retires in favour of a 1-reel * horizontal `ReelSet`. Deferred because that touches core motion and risks * regressing the vertical path. See the "Horizontal reels" row in ROADMAP.md. * * ```ts * const spin = strip.spin(); * strip.setResult([{ visible: ['A','K','Q','J'] }]); // one ColumnTarget (this reel) * await spin; * // main reel reports A and Q were in a win → those cells tumble: * await strip.cascade([0, 2], ['WILD','K']); * ``` */ export declare class HorizontalReel implements Disposable { readonly container: Container; readonly events: EventEmitter; readonly visibleCount: number; private readonly _cellW; private readonly _cellH; private readonly _span; private readonly _windowWidth; private readonly _direction; private readonly _cfg; private readonly _factory; private readonly _tickerRef; private readonly _registeredIds; private readonly _rng; /** Conveyor of `visibleCount + 2` instances: 1 buffer, the window, 1 buffer. */ private readonly _slots; private readonly _M; /** Scroll progress within the current cell, in `[0, span)`. */ private _off; private _state; private _queue; private _resolve; private _spinStart; private _wasSkipped; private _landFrom; private _landT; private _destroyed; private _cascadeT; private _cascadeRemoving; private _cascadeMoving; private _cascadeFinalWindow; private _cascadeWinners; private _cascadeResolve; constructor(cfg: HorizontalReelConfig); get width(): number; get height(): number; get direction(): HorizontalDirection; get isSpinning(): boolean; get isCascading(): boolean; get isDestroyed(): boolean; /** * Start spinning. Resolves with the landed result once {@link setResult} has * been called and the strip settles (or {@link skipSpin} slams it). Mirrors * `ReelSet.spin()`. Throws if not idle. */ spin(): Promise; /** * Hand the reel the round's paying symbols and trigger the stop. Takes the * same `ColumnTarget[]` as `ReelSet.setResult` — this reel is a single column, * so pass exactly one entry whose `visible` holds `visibleCount` ids, * left-to-right. Mirrors `ReelSet.setResult(...)`. Throws if not spinning. */ setResult(symbols: ColumnTarget[]): void; /** * Slam to the result immediately. Mirrors `ReelSet.skipSpin()`. Requires a * result to have been set. */ skipSpin(): void; /** * Tumble the winning cells — a real cascade with removal, the same mechanic * the main reels run, one row wide: * * 1. the `winners` symbols are **removed** (destroyed, they poof out); * 2. the survivors **collapse** toward the settle edge to close the gaps, * keeping their left-to-right order; * 3. `winners.length` **new symbols slide in from the feed edge** — the same * side the spin brings symbols from (`rtl` from the right, `ltr` from the * left) — to fill the freed slots. * * `winners` are visible indices (left-to-right from 0); pass every index for a * full "they all drop". `newIds` are the incoming symbols (one per winner, in * feed order); defaults to random. Resolves once the tumble settles and fires * `cascade:complete`. Throws unless idle (a spin must have landed first). */ cascade(winners: number[], newIds?: string[]): Promise; /** The live symbol instance in visible slot `index`, left-to-right from 0. */ symbolAt(index: number): ReelSymbol; destroy(): void; /** Seed the conveyor: [left buffer, ...window, right buffer]. */ private _layout; private _tick; private _advanceSpin; private _nextFeed; /** Move the conveyor one cell in the travel direction, feeding `id` at the tail. */ private _doShift; private _repaint; /** Position every conveyor slot from its index + the current sub-cell offset. */ private _render; private _beginLanding; private _advanceLanding; private _land; private _advanceCascade; private _finishCascade; private _windowIds; /** Motion sign of the spin: `-1` (rtl, symbols feed from the right) or `+1` (ltr). */ private _feedSign; private _randomId; private _assertResultShape; private _assertRegistered; } //# sourceMappingURL=HorizontalReel.d.ts.map