Overview
The <decides> effect isn't a placed device — it's a core Verse language feature you reach for constantly when building island logic. It marks a function or expression as failable: it either succeeds (and you continue) or fails (and the whole branch is abandoned). Think of a sunny clifftop gate that should only open when a player has stepped on the right pressure plate AND is on the correct team. Each of those checks is a question that can succeed or fail — and <decides> is how you write those questions so they compose cleanly.
The classic beginner mistake is trying to return a logic (true/false) value from a validation helper, then testing it with if (Check(X)?). That fights the language. The idiomatic Verse way is a <decides> function called with square brackets inside an if. If the function's body reaches a failing expression, the if branch simply doesn't run — no exceptions, no null checks.
Reach for <decides> whenever you're asking "is this allowed?", "does this exist?", or "did this succeed?": unwrapping an option, indexing an array, checking a comparison, or verifying a player is on a team before you grant them loot.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: a bright cove with a treasure trigger near the shoreline. When a player steps on it, we run a failable <decides> check — is there a valid agent, and is their index in our "registered explorers" list? Only if that whole check succeeds do we grant them a reward via an item granter.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
cove_treasure := class(creative_device):
# The pressure plate on the shore
@editable
TreasurePlate : trigger_device = trigger_device{}
# Hands the player their reward
@editable
RewardGranter : item_granter_device = item_granter_device{}
# Localized text helper (message params need a localized value, not a raw string)
Announce<localizes>(S : string) : message = "{S}"
# How many explorers we allow to claim treasure this round
var ClaimsRemaining : int = 3
OnBegin<override>()<suspends>:void =
# Subscribe the plate's triggered event to our handler method
TreasurePlate.TriggeredEvent.Subscribe(OnPlateStepped)
# A <decides> helper: succeeds ONLY if claims remain.
# Called with square brackets. Body uses direct failable expressions.
HasClaimsLeft()<decides><transacts>:void =
ClaimsRemaining > 0 # a comparison IS a failable expression
# Event handler method. TriggeredEvent hands us an ?agent.
OnPlateStepped(MaybeAgent : ?agent) : void =
# Unwrap the optional agent with the <decides> ? operator
if (Player := MaybeAgent?):
# Chain another <decides> check with []; both must succeed
if (HasClaimsLeft[]):
set ClaimsRemaining -= 1
RewardGranter.GrantItem(Player)
Line by line:
@editable TreasurePlate/RewardGranter— you MUST declare placed devices as editable fields; a bareTreasurePlate.Method()on an undeclared name fails with Unknown identifier.Announce<localizes>(...)— the pattern for building amessagefrom a string. There is noStringToMessage.HasClaimsLeft()<decides><transacts>:void— a failable function. Note the return type isvoid, notlogic. Its body is just the expressionClaimsRemaining > 0; if that comparison fails, the whole function fails.TreasurePlate.TriggeredEvent.Subscribe(OnPlateStepped)— wire the plate's real event to a method.if (Player := MaybeAgent?)— the?operator turns the optional into a failable expression, unwrapping the agent only when present.if (HasClaimsLeft[])— square brackets because it's<decides>. Parentheses would be a compile error here.RewardGranter.GrantItem(Player)— the actual game effect, only reached when every check succeeded.
Common patterns
Pattern 1 — Unwrap an option with ? (the most common <decides> use). A cove gate that only enables a barrier when a valid agent is present.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
shore_gate := class(creative_device):
@editable
EntryPlate : trigger_device = trigger_device{}
@editable
Barrier : barrier_device = barrier_device{}
OnBegin<override>()<suspends>:void =
EntryPlate.TriggeredEvent.Subscribe(OnEntry)
OnEntry(MaybeAgent : ?agent) : void =
# ? is a <decides> expression: succeeds only if the optional holds a value
if (Player := MaybeAgent?):
Barrier.Disable() # let this player through
Pattern 2 — Safe array indexing is failable. On a clifftop, pick a spawn point from a list — indexing can fail, so it must live inside an if.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
clifftop_picker := class(creative_device):
@editable
StartButton : button_device = button_device{}
@editable
SpawnPads : []player_spawner_device = array{}
OnBegin<override>()<suspends>:void =
StartButton.InteractedWithEvent.Subscribe(OnPressed)
OnPressed(Agent : agent) : void =
# Array access [0] is a <decides> expression — it can fail if empty
if (FirstPad := SpawnPads[0]):
FirstPad.Enable()
Pattern 3 — Writing your own <decides> predicate and composing checks. A dock reward that requires two conditions, both failable, chained with the comma inside an if.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
dock_reward := class(creative_device):
@editable
RewardPlate : trigger_device = trigger_device{}
@editable
Granter : item_granter_device = item_granter_device{}
var GateOpen : logic = false
OnBegin<override>()<suspends>:void =
set GateOpen = true
RewardPlate.TriggeredEvent.Subscribe(OnStepped)
# Custom <decides> predicate: succeeds only when the gate is open
IsGateOpen()<decides><transacts>:void =
GateOpen? # ? on a logic is a <decides> expression
OnStepped(MaybeAgent : ?agent) : void =
# Two failable checks separated by a comma — BOTH must succeed
if (Player := MaybeAgent?, IsGateOpen[]):
Granter.GrantItem(Player)
Gotchas
- Brackets, not parens. A
<decides>function is called with[]:if (HasClaimsLeft[]). WritingHasClaimsLeft()calls it as a normal function and won't compile in a failable context. Conversely,<transacts>/regular functions use(). - Return
void, notlogic. A<decides>function's success/failure IS its result. Don't writereturn true/return false— just put the failable expression as the body. Success = the body succeeded; failure = it failed. <decides>expressions only live in failable contexts. You can call them insideif (...), inside another<decides>function, or wrap them inoption{ ... }. Calling one at the top level ofOnBeginwon't compile.?andoption{...}are two directions of the same bridge.MaybeAgent?unwraps an optional into a<decides>expression;option{ MyDecidesCall[] }wraps a failable call back into an optional. Use whichever the surrounding code needs.- Comma chains are AND.
if (A := X?, B[])succeeds only if both succeed, and later checks can use names bound by earlier ones — great for "valid agent AND meets requirement". messageparams need localized text. Devices that take amessage(like HUD banners) require a<localizes>helper such asAnnounce(S:string); passing a raw"string"fails to compile.- Verse won't convert int↔float for you. A comparison like
ClaimsRemaining > 0is fine (both int), but mixing3 > 0.0errors. Keep your types aligned inside<decides>bodies.