Loading recipe…
The combination most studios get wrong: a sticky wild on a MultiWays slot. The naive implementation forgets where the wild was supposed to be after a few reshapes. so the wild walks downward as shapes shrink and never finds its way back up.
pixi-reels solves this by freezing originRow at pin placement and computing the migrated row as min(originRow, newRows - 1) on every reshape. The pin clamps when the shape can’t fit, and restores when it can again. no wander, no game-state bookkeeping.
The whole mechanic
reelSet.events.on('spin:allLanded', ({ symbols }) => {
for (let c = 0; c < symbols.length; c++) {
for (let r = 0; r < symbols[c].length; r++) {
if (symbols[c][r] !== 'wild' || reelSet.getPin(c, r)) continue;
reelSet.pin(c, r, 'wild', { turns: 3 });
// originRow defaults to the current row. frozen for the pin's lifetime.
}
}
});
Then on every spin, set the shape and result as usual. The engine handles migration:
const promise = reelSet.spin();
reelSet.setShape(rowsPerReel); // migrates pins eagerly to fit the new shape
reelSet.setResult(serverGrid);
await promise;
setShape() is what migrates pins (eagerly, before setResult overlays them). The later AdjustPhase only tweens the visual overlays from their old cell to their new one. the pin map is already at the post-migration rows by then.
Migration semantics
| Spin | Shape (col 2) | originRow | Migrated row | Clamped? |
|---|---|---|---|---|
| 1 | 5 | 4 | 4 | no |
| 2 | 3 | 4 | 2 | yes |
| 3 | 7 | 4 | 4 | no. restored |
| 4 | 4 | 4 | 3 | yes |
Each setShape() call fires pin:migrated per affected pin with { fromRow, toRow, clamped, reelIndex }. clamped: true means the new row was forced by shape size; clamped: false means the pin landed at its origin (either it never moved, or it just restored).
Why this matters
Naive sticky-wild-on-MultiWays implementations track only the current row. The first time a shape shrinks, the pin is rewritten to the clamped row; the original row is forgotten. After a 7 → 3 → 7 cycle, the wild ends up two rows lower than where it started. Players notice. and so do regulators auditing visible state.
originRow is frozen at pin creation. The clamp is computed fresh on every reshape from the origin, not from the previous clamped position. So a 7 → 3 → 7 → 5 → 7 cycle always returns the pin to its origin row whenever the shape allows.
Override originRow if you need to
For walking-wild-on-MultiWays, you’d want to update the origin as the wild walks:
reelSet.pin(newCol, newRow, 'wild', { turns: 'permanent', originRow: newRow });
Default behaviour (origin = placement row) is what 95% of sticky mechanics want.
”Lock at current row, never restore” mode
The default migration: 'origin' clamps + restores, which is right for sticky wilds where the origin row is meaningful. For mechanics where the row is whatever-it-is-now and shouldn’t bounce back on grow (walking wilds, descending wilds, anything path-based), pass migration: 'frozen':
reelSet.pin(2, 4, 'wild', { turns: 'permanent', migration: 'frozen' });
//
// Spin 1: shape 5 → row 4 fits, no migration.
// Spin 2: shape 3 → clamped to row 2; originRow gets updated to 2.
// Spin 3: shape 7 → DOES NOT restore to row 4; stays at row 2.
'frozen' updates originRow on every clamp, so the pin never auto-restores. Pair with movePin() for fully manual position control.
Constraints
- Reshape + AdjustPhase are multiways-only.
pin:migrated,shape:changed, andadjust:*events never fire on non-MultiWays slots. - Pin migration is per-reel. Pins on column 2 don’t reshape when only column 4’s row count changes.
- Big symbols + MultiWays is rejected at build, so a sticky big-symbol-on-MultiWays isn’t a thing in v1.
Related
- Guide: Per-reel geometry, MultiWays & big symbols. full mechanics primer + constraint matrix
- Sticky wild (CellPin). same pattern, non-MultiWays slot
- MultiWays. full MultiWays primer
- Walking wild (movePin). moving pins between cells