Phaser AE

Level saga progression

Pro packv3.0.0-0LogicNew

The meta-game a level-based puzzle wraps around its levels, with the mechanics found in games like Royal Match and Candy Crush Saga. Your puzzle plays the level; this pack decides what it costs, what it pays and what comes next.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 24, 2026

Level saga progression: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

levelSaga

function levelSaga(): Pack<{ levelSaga: typeof kit; }>

Compose the level saga rules under game.levelSaga.

Helpers

createLevelSaga

function createLevelSaga(config: SagaConfig): LevelSaga

Make a saga from its config. Throws, naming the field, on a config it cannot run.

Classes

LevelSaga

class LevelSaga

The meta-game around a level-based puzzle: a map of levels in chapters with hard and super-hard tiers, lives that refill on a clock, coins, stars and boosters, continues with rising prices, win-streak gifts, and timed events (milestone tracks and races against seeded rivals). It never reads a wall clock: time moves only through elapse(seconds) (and away for time the game was closed), so everything replays. It plays no level itself: the game starts one, plays it with its own rules and reports the result. Get one from createLevelSaga.

  • constructor(config: SagaConfig)
  • get config(): SagaConfig — A copy of the config.
  • get state(): SagaState — A copy of the state: everything a save needs.
  • get time(): number — Saga time in seconds.
  • get coins(): number — Coins.
  • get stars(): number — Spendable stars.
  • get streak(): number — Wins in a row.
  • get attempt(): SagaAttempt | null — The level being played, or null.
  • get next(): string | null — The first level not yet won, or null when every level is won.
  • get won(): number — Levels won.
  • lives(): { count: number; max: number; nextIn: number | null; unlimited: number } — Lives now, the maximum, seconds to the next one (null when full or with lives off) and seconds of unlimited lives left.
  • boosters(id: string): number — How many of a booster the player owns.
  • items(name: string): number — The game's collectible count by name.
  • map(chapter?: string): SagaMapLevel[] — Every level with its number, tier, chapter, status and stars; or only one chapter's when chapter is given.
  • chapters(): { id: string; first: number; last: number; won: number; done: boolean }[] — The chapters in order, with their levels' first and last numbers and how many are won.
  • level(id: string): SagaMapLevel | undefined — One level's map entry, or undefined for an unknown id.
  • streakBoosters(): Record<string, number> — The before-boosters the current streak gives the next start (empty below the first tier).
  • continuePrice(): number | null — The price of the next continue in the current attempt, or null when none is left or no level is being played.
  • elapse(seconds: number): SagaEvent[] — Move saga time on by seconds (0 to 31536000): lives refill, unlimited lives run out, events end.
  • away(seconds: number, cap = 28800): SagaEvent[] — Time the game was closed: elapse capped at cap seconds (default 8 hours), so a wrong clock cannot give a day of lives.
  • startLevel(level?: string, boosters: Record<string, number> = {}): SagaResult — Start a level, by default the next one. Takes a life first when lives cost on start, consumes the chosen before-boosters (boosters, by id), adds the streak's free ones, and returns them in the started event for the game to apply.
  • useBooster(id: string): SagaResult — Use a during-booster in the current level.
  • buyBooster(id: string, count = 1): SagaResult — Buy count (1 to 99, default 1) of a booster with coins.
  • buyLives(): SagaResult — Buy a full set of lives with coins.
  • continueLevel(): SagaResult — Buy a continue in the current level at continuePrice(): what it gives (moves, slots) is the game's to apply.
  • finishLevel(result: { won: boolean; stars?: number; items?: Record<string, number> }): SagaResult — End the current level. A win records its best stars (0 to the maximum), pays coins (a first win's by tier, a replay's less), grows the streak, unlocks the next level and moves events on, counting items the game collected. A loss takes a life (when lives cost on failing), ends the streak and can set a race back.
  • abandonLevel(): SagaResult — Give up the current level: a loss.
  • grant(reward: SagaReward, receipt?: string): SagaResult — Give a reward. With a receipt id (1 to 100 characters) it is given once only: a second grant with the same receipt is refused as 'duplicate' and changes nothing.
  • spendStars(count: number): SagaResult — Spend stars (on renovation, say). Refused, changing nothing, when there are not enough.
  • startEvent(def: SagaEventDef): SagaResult — Start a timed event now. An event whose id is running is refused; an ended one with that id is replaced.
  • dropEvent(id: string): SagaResult — Drop an ended event from the state.
  • events(): (SagaEventState & { left: number; standings: { who: string; wins: number }[] })[] — The events, each with seconds left and, for a race, the standings now (the player as 'you', best first).
  • restore(state: SagaState): void — Put back a state state gave, from this config's id. Ids the config no longer has are dropped, not refused.

Types

SagaAttempt

interface SagaAttempt

A level being played.

  • level: string — The level id.
  • tier: SagaTier — Its tier.
  • boosters: Record<string, number> — Before-boosters it started with, bought or from the streak: the game applies them.
  • continues: number — Continues bought in this attempt.
  • used: Record<string, number> — During-boosters used in this attempt.
  • paid: boolean — Whether starting took a life (lives that cost on start), so a win gives it back.

SagaBoosterDef

interface SagaBoosterDef

A booster kind.

  • id: string — Unique id, 1 to 100 characters.
  • when: 'before' | 'during' — 'before': chosen when a level starts and applied from its first move. 'during': used at any point in a level.
  • price?: number — Coins for one; 0 (default) means it cannot be bought.
  • start?: number — How many a new player has; default 0.

SagaChapterDef

interface SagaChapterDef

A run of consecutive levels shown as one area of the map.

  • id: string — Unique id, 1 to 100 characters.
  • levels: number — Levels in it, 1 to 1000. Chapters take levels in order; levels past the last chapter form no chapter.

SagaConfig

interface SagaConfig

The authored saga. Saves are keyed on ids and survive edits to everything else.

  • id: string — Names this game's saves: a save restores only into a config with the same id.
  • levels: number | SagaLevelDef[] — The levels in order: a count (ids level-1 up, tiers from hardEvery and superHardEvery) or definitions; 1 to 100000.
  • hardEvery?: number — With a level count: every n-th level is hard; default 0 (none).
  • superHardEvery?: number — With a level count: every n-th level is super hard, which wins over hard; default 0 (none).
  • chapters?: number | SagaChapterDef[] — Map areas: a level count per area (each chapter-1 up), or definitions; default 20 levels an area.
  • lives?: { max?: number; refill?: number; cost?: 'fail' | 'start' } — Lives: max 0 to 99 (0 turns lives off; default 5), refill seconds a life, 1 to 86400 (default 1800), cost when a life is taken (default 'fail'; 'start' takes it on starting and gives it back on a win).
  • coins?: { start?: number; win?: number; replay?: number; tiers?: Partial<Record<SagaTier, number>>; refill?: number } — Coins: start for a new player (default 0), win for a first win (default 10), replay for winning again (default 0), tiers multipliers (default normal 1, hard 2, super hard 3), refill price of a full set of lives (default 900; 0 means not for sale).
  • stars?: number — Most stars a level can earn, 0 to 5; default 3.
  • starReward?: number — Stars added to the spendable stars per new star earned on a level; default 1.
  • continues?: number[] — Coins for each continue in one attempt, in order; default [900, 1900, 2900]. An empty list allows none.
  • boosters?: SagaBoosterDef[] — Booster kinds.
  • streak?: { wins: number; boosters: Record<string, number> }[] — Free before-boosters for a run of wins: the highest tier the current streak reaches applies to the next start.
  • replay?: boolean — Whether a won level can be played again; default false.
  • seed?: number — Seed for rivals' paces, 0 to 4294967295; default 1.

SagaEvent

type SagaEvent = | { type: 'life-gained'; lives: number } | { type: 'life-lost'; lives: number } | { type: 'started'; level: string; boosters: Record<string, number> } | { type: 'won'; level: string; stars: number; first: boolean; coins: number } | { type: 'lost'; level: string } | { type: 'unlocked'; level: string } | { type: 'chapter-done'; chapter: string } | { type: 'streak'; wins: number } | { type: 'reward'; source: string; reward: SagaReward } | { type: 'milestone'; event: string; step: number; reward: SagaReward } | { type: 'race-finished'; event: string; place: number; reward: SagaReward | null } | { type: 'event-started'; event: string } | { type: 'event-ended'; event: string }

What the saga did, in order.

SagaEventDef

type SagaEventDef = SagaMilestoneEventDef | SagaRaceEventDef

A timed event.

SagaEventState

interface SagaEventState

An event in progress or finished and not yet dropped.

  • def: SagaEventDef — The event's definition.
  • startedAt: number — Saga second it started.
  • endsAt: number — Saga second it ends.
  • points: number — Milestones: points so far.
  • reached: number — Milestones: steps reached (and rewarded).
  • race: SagaRaceState | null — Race standing; null for milestones.
  • ended: boolean — True once it has ended.

SagaLevelDef

interface SagaLevelDef

One level of the map.

  • id: string — Unique id, 1 to 100 characters; progress is saved against it.
  • tier?: SagaTier — Difficulty tier; default 'normal'.

SagaMapLevel

interface SagaMapLevel

A level's place on the map, from map().

  • id: string — Level id.
  • number: number — 1-based position in the saga.
  • tier: SagaTier — Tier.
  • chapter: string | null — Chapter id, or null past the last chapter.
  • status: 'done' | 'next' | 'locked' — 'done' (won), 'next' (the first not won) or 'locked'.
  • stars: number — Best stars, 0 until won.

SagaMilestoneEventDef

interface SagaMilestoneEventDef

A milestone track: points from levels unlock rewards in turn.

  • kind: 'milestones' — 'milestones'.
  • id: string — Unique id among events, 1 to 100 characters.
  • duration: number — How long it runs, in seconds of saga time, 1 to 31536000.
  • points?: { win?: number; perStar?: number; items?: Record<string, number> } — Points for a won level, per star on it, and per collectible reported by finishLevel; default 1 a win.
  • steps: { points: number; reward: SagaReward }[] — The rewards, in order, each at a running total of points (strictly rising).

SagaRaceEventDef

interface SagaRaceEventDef

A race: first to win target levels against rivals who win at their own seeded pace.

  • kind: 'race' — 'race'.
  • id: string — Unique id among events, 1 to 100 characters.
  • duration: number — How long it runs, in seconds of saga time, 1 to 31536000.
  • target: number — Wins needed, 1 to 1000.
  • rivals: number — Rival racers, 1 to 99.
  • pace?: [number, number] — Each rival's seconds per win, drawn once between these; default [120, 600].
  • resetOnLoss?: boolean — True: a lost level sets the player's wins back to 0. Default false.
  • rewards: SagaReward[] — Rewards by finishing place, first place first; a place past the list gets nothing.

SagaRaceState

interface SagaRaceState

A race in progress.

  • wins: number — Player's wins.
  • finishedAt: number | null — When the player reached the target, in saga seconds; null until then.
  • paces: number[] — Each rival's seconds per win.
  • place: number | null — The place the player finished in, from 1, once known; null before.

SagaRefusal

type SagaRefusal = | 'in-level' | 'not-in-level' | 'locked' | 'already-won' | 'no-lives' | 'unknown-level' | 'unknown-booster' | 'wrong-booster-kind' | 'no-booster' | 'not-for-sale' | 'not-enough-coins' | 'no-continues-left' | 'not-enough-stars' | 'lives-full' | 'unknown-event' | 'event-running' | 'bad-count' | 'bad-reward' | 'duplicate'

Why an action was refused.

SagaResult

type SagaResult = { valid: true; reason?: undefined; events: SagaEvent[] } | { valid: false; reason: SagaRefusal; events: [] }

The answer to an action: done, with its events, or refused with a reason and nothing changed.

SagaReward

interface SagaReward

Anything the saga can give. Every field is optional; counts are whole numbers from 0.

  • coins?: number — Coins.
  • lives?: number — Lives, added past the maximum if need be.
  • unlimited?: number — Seconds of unlimited lives, added to any already running.
  • boosters?: Record<string, number> — Boosters, by id.
  • stars?: number — Stars, the currency a renovation or decoration meta spends.
  • items?: Record<string, number> — The game's own collectibles, by name.

SagaState

interface SagaState

Everything that changes; a save holds exactly this.

  • id: string — The config id this state belongs to.
  • time: number — Saga time in seconds: what lives and events run on.
  • lives: number — Lives now.
  • refillAt: number | null — Saga second the next life arrives, or null when lives are full.
  • unlimitedUntil: number — Saga second unlimited lives run out; at or before time means none.
  • coins: number — Coins.
  • stars: number — Spendable stars.
  • items: Record<string, number> — Collectibles, by name.
  • boosters: Record<string, number> — Boosters owned, by id.
  • done: Record<string, number> — Best stars on each won level, by id (0 for a win with no stars).
  • streak: number — Wins in a row.
  • plays: number — Levels started.
  • attempt: SagaAttempt | null — The level being played, or null.
  • events: SagaEventState[] — Events running or ended and not yet dropped.
  • receipts: string[] — Ids of rewards granted with a receipt, so a reward is never given twice.

SagaTier

type SagaTier = 'normal' | 'hard' | 'super-hard'

A level's difficulty tier, which scales its coin reward.