Blocks, Selectors, and Sinking
You follow Juniper back to the middle of the lane, with the gate at your back and both stalls working within earshot. By now the market smells right: sweet bread, lamp smoke, wet canvas, hot brass. It still sounds flat.
Juniper sweeps a hand at the lane. "I need stray bells. A prop here. One omen there. Not the same three details forever." You have seen promising fairs die of repetition. Blocks are how you keep this one moving.
A plain block picks one option
By default, a block chooses one element. Juniper uses that to pull one prop for an ambient note.
A {lantern|bell|map} swings above the lane.
A map swings above the lane.
The result is random by default. In this tutorial, the docs verifier uses a fixed seed so the examples stay checkable.
Sinking packs the next unit tightly
Back on the sign page, Juniper learned that Ranty pays close attention to spacing. Here that matters again, because sound effects and stage cues often need tighter control than ordinary prose.
Wrong attempt
bell {gong} bell
What happened
bell gong bell
Those spaces survived because the block was treated like ordinary text in the sentence.
# The sink removes the spaces that would normally survive around the block.
bell ~{gong} bell
bellgongbell
The ~ sink tells Ranty not to preserve text-style spacing around the next unit.
Repetition, separators, and selector modes
Selectors decide how a block walks through its elements. Here you use one to draft a repeating ambient loop for the lane.
# Repeat four picks, separated by commas, moving forward through the block.
Ambient loop: `[rep:4][sep:", "][sel:[mksel:forward]]{mist|music|tea}
Ambient loop: mist, music, tea, mist
forward walks from left to right and wraps back to the start.
Entanglement keeps distant choices in sync
If two blocks share the same selector handle, they can stay in sync.
# Reuse one selector so both blocks make the same pick.
<$sync = [mksel:one]>
Tavi's omen card reads: `[sel:<sync>]{cat|owl} carries `[sel:<sync>]{lantern|ink}.
Tavi's omen card reads: owl carries ink.
The selector picked once and reused the same choice in both places, giving Tavi one coherent omen instead of two unrelated scraps.
Weights and match selection
@weight changes how likely an element is to be chosen. @on tags an element for match selectors.
# Only the `rare` entries can match, and weight 0 removes one of them.
Rare jar: `[match: rare]{ordinary|meteor-sugar @on rare @weight 1|sleep-dust @on rare @weight 0}
Rare jar: meteor-sugar
Because only the tagged rare elements are eligible, and one of them has weight 0, the result becomes deterministic.
Selector handles are real values
You can store a selector, inspect its type, skip it forward, and freeze it in place. That turns random-looking tables into something you can actually reason about.
# Build a reusable route selector for ambient directions.
<$route = [mksel:forward]>
First route: `[sel:<route>]{north|east|south}\n
Selector kind: `[type: <route>]\n
[sel-skip: <route>]
[sel-freeze: <route>]
Frozen now: `[sel-frozen: <route>]\n
Next route: `[sel:<route>]{north|east|south}
First route: north
Selector kind: selector
Frozen now: @true
Next route: south
More information about blocks, selectors, and sinked text can be found in the Ranty documentation for Blocks, selector, and Text.
The lane starts to breathe. From here you can hear Mira's stall bustle and Tavi's omens stop tripping over themselves. Juniper, hearing the rhythm improve, starts counting under her breath. That makes the next problem obvious: timing.
Previous: Function Calls, Functions, and Ranges | Next: Repetition State, Control Flow, and Attributes