Loading recipe…
Before this change _coordinateBigSymbols only iterated visible rows;
trying to set a big-symbol anchor at bufferAbove[i] or
bufferBelow[i] either threw (exceeds reel height) or silently
failed to paint OCCUPIED stubs. Now the coordinator scans the full
strip range. anchor + h must stay on the strip, but it doesn’t have
to stay in the visible window.
The recipe above lands a 1x3 TALL block with its anchor at row -2
(bufferAbove[1]), so two of the block’s cells are clipped off the
top of the visible window and only the tail shows at row 0. A nudge
DOWN by 2 drags the whole block into view; a nudge UP by 2 pushes it
back out.
The contract
ColumnTarget already exposes bufferAbove and bufferBelow. Put a
big-symbol id at any strip slot and the coordinator handles the rest:
reelSet.setResult([
// Anchor at row -2 (bufferAbove[1]) for a 1x3 block.
// The engine paints OCCUPIED at row -1 and row 0 automatically.
{
visible: [filler(), filler(), filler()],
bufferAbove: [undefined, 'TALL'],
},
// ...other columns
]);
Where the block can live
For a 1xH block on a strip with bufferAbove + visibleRows + bufferBelow
cells, the anchor’s strip index must be in [0, total - h]:
| Anchor at row | Block occupies (row coords) | Visible portion |
|---|---|---|
-h + 1 | [-h+1, 0] | Just the bottom cell at row 0 (the “tail”) |
-1 | [-1, h-2] | All but the topmost cell |
0 | [0, h-1] | Entirely in visible if h <= visibleRows |
visibleRows - h | [visibleRows - h, visibleRows - 1] | Bottom-aligned, all visible |
visibleRows - 1 | [visibleRows - 1, visibleRows + h - 2] | Just the top cell at the last visible row (the “head”) |
Anchors past these bounds throw with a precise message naming the
violated rule (extends past the bottom of the strip or
exceeds reel count for cross-reel overflows).
How it works end-to-end
-
setResult/refill: both call_coordinateBigSymbols(grid)which iterates the full strip range. For every big-symbol anchor it finds (in buffer or visible), it paints OCCUPIED at the block’s non-anchor cells. -
FrameBuilder.TargetPlacementMiddleware. places target symbols into the strip’s symbol array, mapping row coordinates to strip indices viabufferAbove + row. Already handles negative rows. -
StopSequencer/placeSymbols. consume the frame and place realReelSymbols orOccupiedStubs at each strip slot. -
Reel._finalizeFrame. after every snap, two passes:- Scan 1: visible-row anchors (the common case).
- Scan 2: bufferAbove anchors whose blocks extend into visible. For
these,
_occupancy[visibleRow].anchorRowis set to a NEGATIVE value (offset frombufferAbove).
-
getVisibleSymbols/getSymbolAt. index back to the anchor viathis.symbols[bufferAbove + anchorRow], which works whetheranchorRowis positive or negative. -
getSymbolFootprint. returnsanchor.row(possibly negative) so external consumers can locate the anchor cell. -
getBlockBounds. handles negativeanchor.rowby computing pixel coordinates directly from the row offset rather than delegating togetCellBounds(which still rejects negative rows by design).
What still throws
- MultiWays + big symbols. already rejected at build (existing constraint, unchanged).
- Cross-reel blocks via nudge.
w > 1blocks throw insidereelSet.nudge(col, ...). Buffer-row anchors don’t change this: nudging a single reel can’t shift a block whose other-reel cells stay put. - Random-fill big symbols. big symbols must still have
weight: 0so the random provider never picks one (random fill of a 1x1 slot can’t paint a multi-cell block coherently).
Related
- Big symbols MxN. every shape variant.
- Nudge through a big symbol. full-block nudges + the tail-reveal canvas (the in-window mirror of this recipe).
- Peek symbol from buffer-above. the 1x1 analogue: tease a single symbol above the visible window.