Reference Verse compiles

The <decides> Effect: Failable Logic That Actually Compiles

On a sunny cove, you want a treasure chest that only opens for players holding the golden key. That question — 'does this player qualify?' — is exactly what Verse's <decides> effect was built for. This article teaches the decides-effect rule: how failable functions work, how to call them with square brackets, and how they power real gate-keeping on your island.

Updated Examples verified on the live UEFN compiler
Watch the Knotdecides_effect_rule in ~90 seconds.

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 bare TreasurePlate.Method() on an undeclared name fails with Unknown identifier.
  • Announce<localizes>(...) — the pattern for building a message from a string. There is no StringToMessage.
  • HasClaimsLeft()<decides><transacts>:void — a failable function. Note the return type is void, not logic. Its body is just the expression ClaimsRemaining > 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[]). Writing HasClaimsLeft() calls it as a normal function and won't compile in a failable context. Conversely, <transacts>/regular functions use ().
  • Return void, not logic. A <decides> function's success/failure IS its result. Don't write return 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 inside if (...), inside another <decides> function, or wrap them in option{ ... }. Calling one at the top level of OnBegin won't compile.
  • ? and option{...} 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".
  • message params need localized text. Devices that take a message (like HUD banners) require a <localizes> helper such as Announce(S:string); passing a raw "string" fails to compile.
  • Verse won't convert int↔float for you. A comparison like ClaimsRemaining > 0 is fine (both int), but mixing 3 > 0.0 errors. Keep your types aligned inside <decides> bodies.

Build your own lesson with decides_effect_rule

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →