Loading recipe…
Same big-symbols mechanic as the CardSymbol recipe, but every cell is a Spine 2D skeleton. idle plays on land, landing plays as the squash-and-stretch on touchdown, win fires when WinPresenter highlights a payline, and destroy is wired up for cascades. The 2x2 bigWild reuses the regular wild skeleton; Spine scales it to the larger cell box without losing crispness.
Boot the atlas first
SpineReelSymbol looks up its skeleton by alias on activate, so the assets need to be in PIXI’s Assets cache before .build() runs:
import { loadGeneratedSpines, buildSpineMap } from '../../shared/generatedSpineLoader';
await loadGeneratedSpines(); // idempotent. safe to call from every boot
The default base path is '/generated-symbols/'. Both examples/assets/ (publicDir) and the docs site (apps/site/public/generated-symbols/) serve the same 10 JSONs + atlas + page from there.
Mapping ids to skeletons
The bundle ships ten generic skeletons (low_a, low_k, low_q, low_j, mid_1, mid_2, mid_3, high_1, wild, scatter). Map your slot’s symbol ids onto whichever skeleton you want for each:
const SPINE_MAP = {
'9': 'low_a',
'10': 'low_k',
J: 'low_q',
Q: 'low_j',
K: 'mid_1',
A: 'high_1',
wild: 'wild',
bigWild: 'wild', // reuse the wild rig at 2x2 scale
};
builder.symbols((registry) => {
const spineMap = buildSpineMap(SPINE_MAP);
for (const id of Object.keys(SPINE_MAP)) {
registry.register(id, SpineReelSymbol, {
spineMap,
autoPlayLanding: true, // run the landing anim on touchdown
});
}
});
buildSpineMap produces the Record<symbolId, { skeleton, atlas }> shape SpineReelSymbol wants and throws loud if you reference a skeleton name that isn’t in the bundle.
Big-symbol metadata
Same shape as the CardSymbol recipe. the engine doesn’t care what renders the cell; size and weight are what make a symbol “big”:
.symbolData({
wild: { weight: 3, zIndex: 4 },
bigWild: { weight: 0, zIndex: 5, size: { w: 2, h: 2 } },
})
weight: 0 is mandatory for anything bigger than 1x1: random fill must never place a 2x2 because it has no concept of block geometry. The server places it at an anchor cell only, and the engine paints OCCUPIED across the rest of the block. The bigWild registration’s SpineReelSymbol only renders at the anchor; the OCCUPIED cells are rendered by the engine’s invisible stub class.
Animations
The bundled skeletons all expose the canonical four-animation set:
| Anim | When it plays | Behavior |
|---|---|---|
idle | On activate (every land) | Loops; gentle vertical float + micro rotation on the icon bone; frame stays stationary |
landing | On touchdown when autoPlayLanding: true | Squash-and-stretch on root; ends at scale 1 |
win | When WinPresenter calls playWin() | Anticipation dip → punch to 1.22x → bounce settle to 1.0 |
destroy | When playOut() is called (cascades) | Burst to 1.55x while alpha drives to 0 |
idle explicitly resets root.scale and icon.rotate to baseline across its loop, so a destroy’s lingering 1.55x scale is overwritten on the next activate. win and landing settle at scale 1 / alpha ff so they never leave a symbol invisible after their animation ends.
Authoring your own
These ten skeletons are generated by the bun pipeline at tools/symbol-gen/. The animations are authored in a typed DSL, compiled to Spine 4.x JSON, and packed against a shared atlas:
// tools/symbol-gen/scripts/animations/win.ts
export const win = anim('win')
.bone('root', (b) => b
.scale(1.0)
.scaleTo(0.94, frames(4), 'easeOut') // anticipation dip
.scaleTo(1.22, frames(8), 'easeOut') // punch
.scaleTo(0.96, frames(10), 'easeInOut')
.scaleTo(1.04, frames(8), 'easeInOut')
.scaleTo(1.0, frames(8), 'easeInOut') // settle
.scaleTo(1.0, frames(16)) // hold
)
// ...
.build();
Run bun run build in tools/symbol-gen/ to regenerate. Output drops in tools/symbol-gen/out/; copy to examples/assets/generated-symbols/ and apps/site/public/generated-symbols/ to deploy.
Related
- Big symbols (CardSymbol). same mechanic with the no-asset debug class
- MxN big symbols. wide / tall / giant block shapes
- Spine pyramid shape. same skeletons on a non-uniform layout
- Get block bounds. overlay the engine’s view of the block