The Skull Brig: failure contexts and the <decides> operator
In this lesson you'll learn to
- βStudent can write failable functions with <decides><transacts> and call them inside if-contexts so failures roll back instead of crashing.
Student can write failable functions with <decides><transacts> and call them inside if-contexts so failures roll back instead of crashing.
π Reference & full walkthrough: The Skull Brig: failure contexts and the <decides> operator
π§© Your capstone piece: Failable checkpoint validation: ValidateCheckpoint[Racer, Index]<decides> gates lap progress (FAILS compile 2 err β rewrite; also compile-gate verse-failure-contexts which has no compile key; passing failure-context-required + failure-decides back it)
What you'll learn
You'll learn how Verse's failure contexts replace boolean flags. Instead of returning true/false and branching on it, you write code that succeeds when a condition holds and fails (yields zero values) when it doesn't. When a <decides> expression fails inside an if: condition, execution jumps straight to else: β with automatic rollback of any speculative state. We'll pair that with a real UI widget: on failure we call GetPlayerUI[...] and AddWidget to display a text_block error message the player can actually see.
How it works
- The
<decides>effect: A function marked<decides>can fail.IsEnabled[](note the[]β fallible call) succeeds when the device is enabled and fails when it's disabled. It comes from theenableableinterface β only some devices implement it (likehiding_prop_device); a plaintrigger_devicedoes not haveIsEnabled. You may ONLY call<decides>functions inside a failure context (anif/forcondition, another<decides>body, or anot/oroperand). Never call one as a bare statement. - The failure context: Inside
if (A[]):every expression must succeed for thethen:block to run. The moment one fails, Verse skips toelse:and rolls back anything speculative. - Real UI feedback:
GetPlayerUI[Player]is also<decides>β bind it in the condition (if (UI := GetPlayerUI[Player]):), never call it bare. Then build atext_blockβ itsDefaultTextis amessage, not astring, so pass your text through a<localizes>function β and hand it toplayer_ui.AddWidget. That's the payload the player sees. - Cleanup: We keep the widget in a
?text_blockso we canRemoveWidgetit before showing the next message, avoiding stacked errors.
Let's build it
This device listens on a trigger. Each activation it validates a target hiding prop (think: a barrel below decks in the Skull Brig) via a <decides> helper. On success it prints a confirmation; on failure it renders an error text_block on that player's UI.
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Localisation helper β UI text must be message-typed, so route strings through <localizes>.
BrigErrorText<localizes>(Text : string) : message = "Error: {Text}"
# Demonstrates failure contexts driving real player-UI feedback.
failure_feedback_device := class<concrete>(creative_device):
# Trigger the player activates to attempt the interaction.
@editable
ActivationTrigger : trigger_device = trigger_device{}
# The hiding prop (a barrel fits the Skull Brig) we validate before acting on it.
# hiding_prop_device implements the enableable interface, so it exposes IsEnabled[].
@editable
TargetDevice : hiding_prop_device = hiding_prop_device{}
# Track the last error widget so we can remove it before showing a new one.
var MaybeErrorWidget : ?text_block = false
# <decides>: succeeds only if the target is enabled; fails as control flow otherwise.
ValidateTarget(Device : hiding_prop_device)<transacts><decides> : void =
Device.IsEnabled[] # fallible call β this line IS the success condition
# Push an error message onto the given player's UI.
ShowError(Player : player, Text : string) : void =
# GetPlayerUI is <decides>: bind it in the condition, never call bare.
if (PlayerUI := GetPlayerUI[Player]):
# Remove a previous error so messages don't stack up.
if (Old := MaybeErrorWidget?):
PlayerUI.RemoveWidget(Old)
# Build the widget β DefaultText is a message, so use the <localizes> helper.
ErrorText := text_block{DefaultText := BrigErrorText(Text)}
PlayerUI.AddWidget(ErrorText)
set MaybeErrorWidget = option{ErrorText}
# Runs when the trigger fires. Payload is the activating agent (may be empty).
OnActivated(MaybeAgent : ?agent) : void =
# Bind the agent and resolve it to a player, all in one failure context.
if (Agent := MaybeAgent?, Player := player[Agent]):
if (ValidateTarget[TargetDevice]): # succeeds only if TargetDevice is enabled
Print("Interaction succeeded!")
else:
# Runs automatically when ValidateTarget fails.
ShowError(Player, "Target device is disabled")
OnBegin<override>()<suspends> : void =
# Device events subscribe WITHOUT parentheses on the event field.
ActivationTrigger.TriggeredEvent.Subscribe(OnActivated)
Try it yourself
- Place a Trigger device, a Hiding Prop device (pick the barrel β very Skull Brig), plus this device. Assign the trigger to
ActivationTriggerand the prop toTargetDevice. - In the
TargetDevicedetails, set Enabled at Game Start to Off soIsEnabled[]fails. - Play the level, walk onto
ActivationTrigger, and watch the redtext_blockerror appear on your HUD. - Flip
TargetDeviceback to Enabled and trigger again β now you see thePrintconfirmation instead of the widget. - Trigger repeatedly while disabled: the previous widget is removed first, so errors never stack.
Recap
<decides>functions represent computations that may fail β call them with[]and only inside a failure context.- An
if (Cond[]):runsthen:only when every condition succeeds; otherwise it jumps toelse:and rolls back speculative state. GetPlayerUI[Player]is itself<decides>β bind it, never call it bare.- Real feedback means actually building a
text_block(itsDefaultTextis amessageβ use a<localizes>helper, never a rawstring), callingAddWidget, and tracking it in a?text_blockso you canRemoveWidgetit later. - Device events like
TriggeredEventsubscribe without parentheses on the event field.
Keep going β free
You've read the intro. The rest of this lesson is free for members. Sign in to continue and track your progress.
Sign in free to continue this lessonSources
/guides/sug-implementing-error-handling-with-failure-contexts-and-the-deBiloxi Studios original lesson
Add this lesson to your free study plan.
π§ The Keeperβs log
Quest complete? Chart your next heading from the π¦ West Coves expedition.
β΅ Take me there