Overview
This genre pack targets puzzle games in the Bejeweled / Candy Crush / Puzzle & Dragons mould: a board defined by a score resource and a move economy, a swap / match / cascade loop, and — at its core — a match-scoring pipeline in which a base match score is multiplied by a combo factor (consecutive matches) and a chain factor (cascade depth). Each move is a spent resource; running out of moves ends the level.
It is the practical, copy-and-extend companion to the Puzzle (2048-style) case study in
the core specification (Section 16.4). Where the case study sketches the
grid-cell attribute set, the move ability that drives state through Tasks, and the principle that
board state is mutated only through Effects (never by direct tag writes — §3.1), this pack ships the
matching schema-conformant Attributes, Tags, Abilities, and Effects in entities/, plus a worked
board in entities/gameplay_controller.yaml. The case study uses 2048’s slide-and-merge grid as its
concrete model; this pack uses tile-matching (match-3) as its concrete seed, but the same pieces —
the scoring factors, the move economy, and the cascade chain — generalize across the wider
puzzle family (physics, logic, puzzle-platformer) catalogued under
family-puzzle in the taxonomy.
Relationship to the core specification
This document is additive. It defines genre-specific Attributes, Tags, Abilities, and Effects on top of the core Universal Gameplay Ability System specification.
-
It MUST NOT redefine, override, or contradict any concept in the core spec.
-
All entities in
entities/validate against the sameschemas/*.jsonas the core. -
Board state is mutated only through Effects (core spec §3.1, echoed by the §16.4 case study) — abilities gate on tags and apply Effects; they never write tags directly.
-
The multiplicative combo×chain scoring it relies on is a core
ExecutionCalculation(core spec §9.5) —ExecCalc_MatchScore— not a new construct. The combo and chain counters themselves are ordinary attributes accumulated by additive Effect modifiers; only the final multiply-and-bank lives in the Execution.
Genre Attributes
Defined in entities/attribute_set.yaml as the PuzzleBoardSet set. The base values describe a
default match-3 level (target 1000, 25 moves, base match value 100), so the set is playable as
shipped.
| Attribute | Category | Role |
|---|---|---|
|
Resource |
Accumulated level score, clamped to |
|
Statistic |
The score goal for the level; reaching it grants |
|
Statistic |
Points per match before the combo and chain multipliers; the |
|
Statistic / Resource |
|
|
Statistic / Resource |
Optional timed-mode budget, clamped to |
|
Statistic |
Consecutive-match multiplier, clamped to |
|
Meta |
Cascade depth of the current resolution ( |
|
Meta |
Telemetry written by |
The match-scoring pipeline (the core puzzle mechanic)
Scoring a match is pushed on from three directions at once — the size of the match, how many matches the player has strung together, and how deep the resulting cascade runs. This pack expresses that as a single product:
where \(b\) is BaseMatchValue (scaled by TilesCleared for larger matches),
\(m_{\text{combo}}\) is the aggregated ComboMultiplier, and \(d_{\text{chain}}\) is
the current ChainLength.
The two scoring factors
The combo and chain multipliers are each accumulated as an ordinary attribute through additive
Effect modifiers — there is no Channel: field on any entity. GE_ComboBuild adds +1 to
ComboMultiplier per match and GE_CascadeChain adds +1 to ChainLength per cascade; the two are
never hand-multiplied in data. The multiply across the two factors is performed once, at scoring
time, inside the ExecCalc_MatchScore Execution (see below) — that is where the product is formed and
banked.
| Factor | What feeds it | Accumulation |
|---|---|---|
Combo |
|
Additive into |
Chain |
|
Additive into |
GE_ComboBuild is a HasDuration effect that refreshes its own timer on every application:
because re-applying a HasDuration effect restarts its remaining duration, a fast run of matches
keeps the window alive and the multiplier climbing (1 → 2 → 3 …); the instant the player lets the
2.5 s window lapse, the effect expires and ComboMultiplier falls back to its clamped floor of 1.
This is the puzzle analogue of the shooter pack’s held-state effects — the genre’s "feel" expressed
as a refreshing duration rather than a one-shot modifier.
Per-match product via ExecutionCalculation
The match score is a product of four live inputs (TilesCleared, BaseMatchValue,
ComboMultiplier, ChainLength), so it cannot be a static ScalableFloat modifier — multiplying
two runtime attributes together is exactly the case the core reserves for a custom
ExecutionCalculation (§9.5). GE_MatchClear (entities/effect_match_clear.yaml) therefore
delegates to ExecCalc_MatchScore, which reads the four attributes, adds the product to Score, and
mirrors it into LastMatchScore for the UI. This is the puzzle counterpart of the Racing pack’s
traction calculator and the Shooter pack’s hit-resolution calculator — the seam where a genre’s
signature math plugs into UGAS. The combo and chain factors still accumulate declaratively as plain
attributes in effect YAML; the calculation only performs the final multiply-and-bank.
entities/gameplay_controller.yaml)A match-3 board caught mid-cascade with an active combo. The player’s fifth swap forms a line of
three (BaseMatchValue 100) while the combo multiplier stands at 3 and the clear triggers a
single cascade (ChainLength 1, chain factor \(1 + 1 = 2\)):
-
This match: \(100 \times 3 \times (1 + 1) = 600\) (base × combo × chain) — banked in
LastMatchScore. -
Running
Score: \(100 + 200 + 600 = 900\) — the sum of three scoring matches at combos ×1, ×2, ×3. -
MovesRemaining: \(25 - 5 = 20\) — five swaps spent (two formed no match but still cost a move).
These are exactly the CurrentValue entries recorded for the example board. Score 900 is still
short of TargetScore 1000 with 20 moves in hand, so the board remains in Puzzle.Phase.Playing.
The move economy
Every swap is a spent resource. GA_SwapTiles (entities/ability_swap_tiles.yaml) declares
Cost: GE_MoveCost, a trivial effect that subtracts 1 from MovesRemaining. Because an ability
cannot activate if it cannot pay its cost, this single cost gate is exactly what ends the level at
zero moves — no explicit "out of moves" check or tag is needed. This is the direct parallel of the
Shooter pack, where the ammo cost on GA_Fire is what stops the gun firing on an empty magazine: in
both genres a depleted resource silently closes the loop. GA_ActivatePowerup draws from the same
MovesRemaining budget, so powerups trade against ordinary moves; GA_UseHint is free but rate-
limited by a cooldown. GE_MoveCost and GE_HintCooldown are trivial (a flat resource cost and a
cooldown tag) and are referenced by name rather than shipped as files — the same convention the
Racing and Shooter packs use for their cost and cooldown effects.
Genre Tags
Defined in entities/tag_registry.yaml (additive only):
-
Board resolution states —
Puzzle.State.Matching,Puzzle.State.Cascading,Puzzle.State.Stuck, all granted by Effects. -
Level phase —
Puzzle.Phase.Playing,Puzzle.Phase.Cleared,Puzzle.Phase.GameOver. -
Tile types —
Tile.Type.Red|Blue|Green|Yellow|Special. -
Powerup classification —
Powerup.Type.Bomb|Line|Color. -
Ability types —
Ability.Type.Swap|Powerup|Hint.
Genre Abilities
-
entities/ability_swap_tiles.yaml—GA_SwapTiles: the core move;Cost: GE_MoveCost(−1MovesRemaining), waits for the adjacent-tile confirmation, then appliesGE_MatchClear. Blocked while the board is resolving (Matching/Cascading) orStuck; requiresPuzzle.Phase.Playing. -
entities/ability_activate_powerup.yaml—GA_ActivatePowerup: detonates a selected special tile, applyingGE_PowerupBomb; also costs a move. RequiresTile.Type.Special. -
entities/ability_use_hint.yaml—GA_UseHint: a no-cost assist on a cooldown (Cooldown: GE_HintCooldown) that highlights a legal move.
Genre Effects
-
entities/effect_match_clear.yaml—GE_MatchClear: instant; runsExecCalc_MatchScoreto bank the match product intoScore; grantsPuzzle.State.Matching. -
entities/effect_combo_build.yaml—GE_ComboBuild:HasDuration;+1ComboMultiplier, refreshing its window on every match (the combo factor). -
entities/effect_cascade_chain.yaml—GE_CascadeChain: instant;+1ChainLengthper cascade (the chain factor); grantsPuzzle.State.Cascading. -
entities/effect_powerup_bomb.yaml—GE_PowerupBomb: instant; anAttributeBasedflatScorebonus scaled offBaseMatchValue; grantsPuzzle.State.Cascading. -
entities/effect_time_bonus.yaml—GE_TimeBonus: instant;+TimeRemainingfor timed modes. -
entities/effect_reset_chain.yaml—GE_ResetChain: instant;Override`s `ChainLengthback to0once the board settles; grantsPuzzle.Phase.Playing.
Using this pack
-
Copy
genres/puzzle/into your project (or loadentities/directly via theugas-schema-authorskill). -
Tune the level shape in
PuzzleBoardSetfirst —TargetScore,MaxMoves, andBaseMatchValueset the difficulty — then add tile types asTile.Type.tags and new powerups asPowerup.Type.effects. -
Author new scoring sources by feeding the right factor: consecutive-play bonuses add into
ComboMultiplier, cascade-depth bonuses intoChainLength; reserve theExecCalc_MatchScorecalculation for the final multiply-and-bank. -
Implement the one
ExecCalc_MatchScorehook in your engine (the per-match product); everything else is data. -
Add new states as
Puzzle.State./Puzzle.Phase.tags granted by Effects (never mutate tags directly), and new moves as Effects + Abilities. -
Validate with
python scripts/validate_schema_examples.pybefore committing.