π¦ West Coves
Teen
23 lessons Β·
~184 min Β· Generated 2026-07-04
REAL UEFN island for west-coves must be a water-course archipelago: a sheltered lobby lagoon (Marooned Town dock) with player spawn pads + a matchmaking-portal return link to AweShucks Town, then a looped boat channel threading mangrove gates, sea-stack pillars, Kraken's Cove and
Learning Outcomes
Lesson 1: Shout into the Void: print/log debugging
Objectives
- Student can instrument any Verse script with Print statements and read the output log to trace game flow and diagnose bugs.
Student can instrument any Verse script with Print statements and read the output log to trace game flow and diagnose bugs.
π Builds on: north-jungle: verse language fundamentals (first-script, functions)
π§© Your capstone piece: The RaceDebug logger used in every later step (toggleable debug overlay of race state)
Quiz
- How many arguments does the message part of Print() accept?
- A. As many comma-separated values as you like
- B. Exactly one string
- C. One string per line
- D. A string and a number
- What does Print("Total = {3 + 4}") write to the log?
- A. Total = {3 + 4}
- B. Total = 34
- C. Total = 7
- D. Total = 3 + 4
- Why is GetPlayerUI called with square brackets β GetPlayerUI[] β instead of parentheses?
- A. It returns a logic value
- B. It is fallible and must be used in a failure context
- C. It is a transacts function
- D. Square brackets are optional styling
- Which call actually places a widget onto a player's screen?
- A. Print(Widget)
- B. UI.AddWidget(Canvas)
- C. Widget.Show()
- D. GetPlayspace().DisplayUI()
- How do you build a debug output string in Verse?
- A. "Count = " + Count
- B. String interpolation: "Count = {Count}"
- C. Concat("Count = ", Count)
- D. Print("Count = ", Count)
Answer key
- B β Print takes a single string; combine values using string interpolation, not extra arguments.
- C β The braces evaluate the expression before converting to text, so {3 + 4} becomes 7.
- B β GetPlayerUI[] can fail, so it must be bound inside a failure context such as an if condition.
- B β After binding UI via GetPlayerUI[], you call UI.AddWidget(Canvas) to display it.
- B β Verse has no string + concatenation for this; use interpolation with curly braces.
Recap
One step closer to Coastal Race β The RaceDebug logger used in every later step (toggleable debug overlay of race state)
Sources
- /guides/sug-using-print-for-verse-debugging
Lesson 2: Readable Code Is Debuggable Code: naming conventions
Objectives
- Student can apply Verse naming conventions (PascalCase functions, descriptive vars, _device suffixes) so bugs are visible on read.
Student can apply Verse naming conventions (PascalCase functions, descriptive vars, _device suffixes) so bugs are visible on read.
π§© Your capstone piece: Naming standard for the whole Coastal Race codebase (RaceConfig, CheckpointGate, OnRacerEliminated)
Quiz
- Which casing convention does Verse use for class names?
- A. PascalCase (e.g., VaultDoorController)
- B. snake_case (e.g., vault_door_controller)
- C. SCREAMING_SNAKE_CASE (e.g., VAULT_DOOR_CONTROLLER)
- D. camelCase (e.g., vaultDoorController)
- You have an @editable field that holds a trigger_device. Which name best follows Verse conventions?
- A. pressure_plate
- B. pressureplate
- C. PressurePlate
- D. PRESSURE_PLATE
- What is the recommended file-naming pattern for a Verse file containing a creative_device class that manages scoring?
- A. ScoreManager.verse
- B. score_manager_controller.verse
- C. scoremanager.verse
- D. Score_Manager_Controller.verse
- Why should numeric fields include their unit in the name (e.g., DurationSeconds instead of Duration)?
- A. The Verse compiler requires units in field names to type-check numbers
- B. Verse has no unit type system, so the name is the only documentation of what unit is expected
- C. UEFN's Details panel automatically converts values based on the unit suffix
- D. It is required by the @editable specifier
- You want to display a message to a player using a hud_message_device. You try passing a raw string literal but the compiler rejects it. What must you do instead?
- A. Cast the string with StringToMessage()
- B. Declare a function with the <localizes> specifier that returns a message, then call it
- C. Wrap the string in double curly braces: {{"my text"}}
- D. Use Print() instead, which accepts raw strings
Answer key
- B β Verse class names use snake_case, matching the standard library (creative_device, trigger_device, barrier_device). Fields, methods, and local variables use PascalCase.
- C β @editable fields (and all non-class identifiers) use PascalCase. The name also appears verbatim in the UEFN Details panel, so a clear PascalCase name is important for designers.
- B β File names follow snake_case with a role suffix: [system]_[role].verse. For a creative_device the role suffix is _controller, giving score_manager_controller.verse.
- B β Verse does not have a built-in unit type system. A field named Duration is ambiguous β seconds? milliseconds? frames? Including the unit in the name (DurationSeconds) is the only way to make the expectation clear to callers and designers.
- B β Wherever a message type is required you must provide a value produced by a <localizes> function. There is no StringToMessage() in Verse. Declare e.g. MyMsg<localizes>(S:string):message = "{S}" and pass MyMsg("your text").
Recap
One step closer to Coastal Race β Naming standard for the whole Coastal Race codebase (RaceConfig, CheckpointGate, OnRacerEliminated)
Sources
- /guides/naming-conventions
Lesson 3: Crow's Nest Constants: immutable bindings
Objectives
- Student can declare constants and immutable data bindings and explain why config values should never be mutable.
Student can declare constants and immutable data bindings and explain why config values should never be mutable.
π§© Your capstone piece: RaceConfig constants: LapCount, StormTimeoutSeconds, MinRacers, CheckpointCount
Quiz
- Which syntax is valid for declaring a constant at class scope in Verse?
- A. RaceTime : float = 90.0
- B. RaceTime := 90.0
- C. var RaceTime : float = 90.0
- D. const RaceTime = 90.0
- A timer_device's SuccessEvent sends which type to its handler?
- A. agent
- B. ?agent
- C. player
- D. void
- You want to add 10 seconds to a player's remaining race time. Which pair of timer_device methods do you use?
- A. GetActiveDuration then SetActiveDuration
- B. Resume then Pause
- C. Start then Complete
- D. Save then Load
- Why is `GracePeriodTime : float = 10.0` better than writing `10.0` directly in a SetActiveDuration call?
- A. It makes the code run faster at runtime.
- B. It gives the value a meaningful name, making intent clear and changes a one-line edit.
- C. It prevents the compiler from optimising the value away.
- D. Constants are required by SetActiveDuration β literals are not accepted.
- You write `Count := 3` inside a function body. What is the type of Count?
- A. float, because Verse defaults to float
- B. int, inferred from the integer literal 3
- C. This is a compile error β you must write Count : int = 3
- D. string, because := always creates a string
Answer key
- A β At class scope you must provide an explicit type annotation: `Identifier : type = value`. The shorthand `:=` (inferred type) is only valid inside a function body. `var` declares a mutable variable, not a constant. Verse has no `const` keyword.
- B β SuccessEvent is typed `listenable(?agent)` β it sends an optional agent. You must unwrap it with `if (A := MaybeAgent?):` before using the agent value.
- A β GetActiveDuration(Agent) reads the current remaining seconds; SetActiveDuration(NewTime, Agent) writes the updated value. Together they let you add or subtract time from a live countdown.
- B β Constants are a readability and maintainability tool. A named constant documents what the number means and ensures that if you need to change it, you only edit one place. SetActiveDuration accepts any float expression β literals work too, but named constants are best practice.
- B β Inside a function, `:=` lets Verse infer the type from the initializer. The literal `3` is an integer literal, so Count is inferred as int. The explicit form `Count : int = 3` is equivalent and also valid.
Recap
One step closer to Coastal Race β RaceConfig constants: LapCount, StormTimeoutSeconds, MinRacers, CheckpointCount
Sources
Lesson 4: Maybe Treasure, Maybe Nothing: returning and unwrapping option types
Objectives
- Student can return ?t from a function and safely unwrap it with if-then binding and the ? operator.
Student can return ?t from a function and safely unwrap it with if-then binding and the ? operator.
π Builds on: north-jungle: verse-optionals fundamentals
π§© Your capstone piece: FindWinner()?player β the safe race-winner lookup (companions: verse-optionals PASS, sug-handling-optional-types PASS, sug-safe-optional-value-access PASS)
Quiz
- How do you write the type 'maybe an int' in Verse?
- A. int?
- B. ?int
- C. option<int>
- D. maybe int
- What value represents an empty option in Verse?
- A. nil
- B. null
- C. false
- D. 0
- Which idiom correctly unwraps an option named Maybe?
- A. if (X := Maybe?):
- B. X = Maybe.value
- C. if (Maybe != nil):
- D. X := Maybe!
- Why must option unwrapping happen inside an `if` or `for`?
- A. For performance reasons
- B. Because `?` is a failable expression needing a failure context
- C. It doesn't; you can unwrap anywhere
- D. Because options are async
- How does FindBuoyIndex signal 'no match'?
- A. return nil
- B. return -1
- C. return false
- D. throw an error
Answer key
- B β A leading `?` marks an option type, so `?int` means 'an int or nothing'.
- C β An empty option is `false`; there is no `nil` or `null` in Verse.
- A β `if (X := Maybe?):` uses the query operator in a failure context, binding X only when a value is present.
- B β The query operator `?` can fail (empty option), so it may only appear in a failure context like an `if`/`for` condition.
- C β It returns `false`, the empty option, so the caller can handle the missing case.
Recap
One step closer to Coastal Race β FindWinner()?player β the safe race-winner lookup (companions: verse-optionals PASS, sug-handling-optional-types PASS, sug-safe-optional-value-access PASS)
Sources
Lesson 5: The Skull Brig: failure contexts and the <decides> operator
Objectives
- 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.
π§© 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)
Quiz
- Why is the `[]` bracket syntax used when calling `IsEnabled`?
- A. It indexes an array element
- B. It marks a fallible `<decides>` call that must appear in a failure context
- C. It converts the return value to a logic
- D. It calls the function asynchronously
- What happens when a `<decides>` expression fails inside an `if` condition?
- A. The program crashes
- B. An exception is thrown
- C. Execution jumps to `else:` and speculative state is rolled back
- D. The function returns false
- Why must `GetPlayerUI[Player]` be bound inside an `if` condition?
- A. Because it is `<suspends>`
- B. Because it is `<decides>` and can fail
- C. Because it returns a string
- D. Because it modifies persistent state
- How do you subscribe to a trigger device's fired event?
- A. ActivationTrigger.TriggeredEvent().Subscribe(OnActivated)
- B. ActivationTrigger.TriggeredEvent.Subscribe(OnActivated)
- C. ActivationTrigger.Subscribe(TriggeredEvent)
- D. Subscribe(ActivationTrigger.TriggeredEvent)
- What is the correct way to build an error message that includes a variable in Verse?
- A. "Error: " + Text
- B. Print("Error:", Text)
- C. "Error: {Text}" using interpolation
- D. concat("Error: ", Text)
Answer key
- B β `[]` invokes a `<decides>` (fallible) function; it may only appear inside a failure context like an `if` condition.
- C β Failure is control flow: on failure Verse skips to `else:` and automatically rolls back any speculative changes.
- B β `GetPlayerUI` is a `<decides>` function, so it must be called with `[]` inside a failure context, e.g. `if (UI := GetPlayerUI[Player]):`.
- B β Device events subscribe WITHOUT parentheses on the event field: `ActivationTrigger.TriggeredEvent.Subscribe(OnActivated)`.
- C β Verse has no string `+` concatenation; use interpolation like `"Error: {Text}"`.
Recap
One step closer to Coastal Race β 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)
Sources
- /guides/sug-implementing-error-handling-with-failure-contexts-and-the-de
Lesson 6: Sunken Deck Salvage: array indexing is failable
Objectives
- Student can index arrays inside failure contexts and never write an out-of-bounds crash again.
Student can index arrays inside failure contexts and never write an out-of-bounds crash again.
π Builds on: north-jungle: arrays fundamentals (sum-an-array)
π§© Your capstone piece: Safe Checkpoints[Index] lookups as racers advance through the gate array
Quiz
- What happens in Verse when you write `MyArray[5]` and the array only has 3 elements?
- A. The expression fails, and if it is inside an `if`, the else branch runs
- B. Verse throws a runtime exception and halts the device
- C. Verse returns a default zero value for the element type
- D. The expression succeeds but returns an invalid reference
- Which of the following is the correct way to safely read the first element of `MyArray` in Verse?
- A. First := MyArray[0]
- B. if (First := MyArray[0]): # use First
- C. First := MyArray.Get(0)
- D. try { First := MyArray[0] } catch {}
- You want to iterate over every element in `Triggers : []trigger_device` and call `Disable()` on each. Which approach is safest and most idiomatic?
- A. for (I : 0..Triggers.Length - 1): Triggers[I].Disable()
- B. for (Trigger : Triggers): Trigger.Disable()
- C. var I : int = 0; loop: if (T := Triggers[I]): T.Disable(); set I += 1
- D. Triggers[0].Disable(); Triggers[1].Disable() # hardcoded
- You have an `@editable` array of `item_granter_device` but your device does nothing at runtime. What is the most likely cause?
- A. Arrays cannot be marked @editable in Verse
- B. You forgot to add elements to the array in the UEFN editor, so it is empty
- C. item_granter_device is not a valid array element type
- D. You must call MyArray.Initialize() before using it
- Inside a single `if` condition, you chain two failable array accesses: `if (A := Arr[I], B := Arr[I+1]):`. What happens if only the second access fails?
- A. The body runs with A valid and B set to a default value
- B. Only the line using B is skipped; A is still usable in the body
- C. The entire if body is skipped because the whole condition fails
- D. A compile error is raised because chaining failable expressions is not allowed
Answer key
- A β Array indexing is a failable expression in Verse. An out-of-bounds index causes the expression to fail, which is handled by the surrounding failure context (if/else, for, etc.) β no crash, no exception.
- B β Square-bracket indexing is failable and must appear inside a failure context such as an `if` expression. A bare assignment outside a failure context is a compile error. Verse has no `try/catch` and no `.Get()` method on arrays.
- B β `for (Trigger : Triggers)` iterates elements directly and is always bounds-safe β it simply produces no iterations on an empty array. The range-based approach in option A can fail if the array is empty (Length - 1 = -1).
- B β @editable arrays default to empty (`array{}`). If you do not wire up elements in the UEFN editor, every index access fails silently. Always check `Length > 0` in OnBegin during development.
- C β In Verse, a failure anywhere in a chained `if` condition causes the entire condition to fail, and the whole body is skipped. Partial success within a single condition chain is not possible.
Recap
One step closer to Coastal Race β Safe Checkpoints[Index] lookups as racers advance through the gate array
Sources
- /guides/array-bounds-are-failable
Lesson 7: Racer Roster: adding and removing from arrays
Objectives
- Student can grow, shrink, and rebuild arrays (concat, slice-around-index) to maintain a live roster.
Student can grow, shrink, and rebuild arrays (concat, slice-around-index) to maintain a live roster.
π§© Your capstone piece: The live Racers array: add on join, remove on elimination/leave
Quiz
- You call `MyArray.RemoveAllElements(0)` but the array still contains zeros afterward. What went wrong?
- A. You forgot to assign the result back: `set MyArray = MyArray.RemoveAllElements(0)`
- B. RemoveAllElements only removes the first occurrence, not all of them
- C. RemoveAllElements requires a failure context like an `if` expression
- D. You must call `MyArray.Clear()` first
- Which syntax correctly appends a single `player` value `P` to a `var Roster : []player`?
- A. set Roster += P
- B. set Roster += array{P}
- C. Roster.Add(P)
- D. set Roster = Roster.Append(P)
- Why must `Remove[From, To]` be called inside an `if` expression?
- A. Because it returns a tuple, not an array
- B. Because it is a suspending function that requires an async context
- C. Because it is failable β it fails when the index range is out of bounds
- D. Because it mutates the array in place and needs a transaction
- You declare `var Bag := array{}` and get a compile error. What is the fix?
- A. Use curly braces: `var Bag := {}`
- B. Add a type annotation: `var Bag : []int = array{}`
- C. Initialize with at least two elements
- D. Use `var Bag := array{0}.Remove[0,0]`
- A `trigger_device` TriggeredEvent hands your handler `(Agent : ?agent)`. Before you can call `player[Agent]`, what must you do?
- A. Cast Agent with `(player)Agent`
- B. Call `Agent.GetPlayer()`
- C. Unwrap the option with `if (A := Agent?)` and then use A
- D. Nothing β ?agent and agent are interchangeable
Answer key
- A β Verse arrays are value types. RemoveAllElements returns a NEW array with the elements removed β it does not mutate the original. You must write `set MyArray = MyArray.RemoveAllElements(0)` to keep the change.
- B β The `+=` operator on arrays is concatenation of two arrays. You must wrap the single item in `array{P}` to make it a one-element array before concatenating.
- C β `Remove` uses square-bracket call syntax (`[]`) which signals a failable expression in Verse. If the indices are out of range it fails, so it must live inside a failure context such as `if (Result := MyArray.Remove[0, 0])`.
- B β When you create an empty array Verse cannot infer the element type from the literal alone. You must provide an explicit type annotation such as `var Bag : []int = array{}` so the compiler knows what kind of array it is.
- C β The event delivers an *optional* agent (`?agent`). You must unwrap it with `if (A := Agent?):` to get a concrete `agent` value before you can attempt the `player[A]` downcast.
Recap
One step closer to Coastal Race β The live Racers array: add on join, remove on elimination/leave
Sources
Lesson 8: Sea-Stack Pillars: your shared helper module
Objectives
- Student can extract reusable functions into a module and import it from any file β the SOLID layering habit every later zone uses.
Student can extract reusable functions into a module and import it from any file β the SOLID layering habit every later zone uses.
π Builds on: north-jungle: verse-modules + creating-custom-modules
π§© Your capstone piece: RaceHelpers module (FormatTime, SafeIndex, Logging) imported by every subsequent lesson (content/verse/shared-helper-module.json compile-PASSED; backed by using-a-module + function-as-helper, both PASS)
Quiz
- A device file calls FormatTime(90) but fails to compile: "Unknown identifier FormatTime". The RaceHelpers module (with FormatTime<public>) lives in another file in the same project. What fixes the build?
- A. Add `using { RaceHelpers }` at the top of THIS file β every file that calls a module's functions needs its own import line.
- B. Add `using { /Fortnite.com/Devices }` β device imports also bring in project modules.
- C. Copy FormatTime into the device class β module functions can only be called inside their own file.
- D. Rename the call to RaceHelpers.FormatTime(90) β qualified calls never need a using directive.
- Why must helper functions inside a module carry the <public> specifier?
- A. Without <public>, the function is internal to the module β files that import the module cannot see or call it.
- B. <public> makes the function show up as an @editable field in the Details panel.
- C. <public> is required for any function that takes parameters.
- D. It marks the function as thread-safe for spawn{} calls.
- Inside FormatTime, why are Quotient and Mod called with square brackets inside an `if:` block?
- A. Both are <decides> (failable) functions β square brackets mark the failable call, and it must happen in a failure context like if.
- B. Square brackets are how Verse passes two arguments to math functions.
- C. The if: block is only needed because the function returns a string.
- D. Quotient and Mod are asynchronous, so they need a suspending context.
- You want the module to own the checkpoint pressure plate directly, so you add `@editable Plate : trigger_device = trigger_device{}` inside the module block. What happens?
- A. Compile error β modules are not devices and cannot hold @editable fields; pass placed devices to module functions as parameters instead.
- B. It compiles, and the plate appears in every importing device's Details panel.
- C. It compiles, but only the first device to import the module can wire it.
- D. It works only if the module is also marked <public>.
- The demo prints `RaceLog("A 754-second run reads as {FormatTime(754)}")`. What does FormatTime(754) return?
- A. "754:00" β the function just appends ":00" to the input.
- B. "12:34" β Quotient[754, 60] gives 12 minutes and Mod[754, 60] gives 34 seconds.
- C. "12:3" β Mod returns 3 because it truncates.
- D. "0:00" β 754 is out of range for the race clock.
Answer key
- A β A `using { RaceHelpers }` directive only applies to the file it is written in. The module being <public> and present in the project is not enough β each consumer file must import it. (Fully qualified paths can also work, but the module still has to be resolvable from that file's scope; the standard fix is the using line.)
- A β Access control in Verse modules defaults to internal. Only members marked <public> are visible to code outside the module, so shared helpers must be <public> to be usable by importers.
- A β Quotient<native><public>(X:int, Y:int)<computes><decides>:int and Mod share the same failable signature β division can fail (e.g. divide by zero). Failable invocations use [] and must run in a failure context; the if: block provides it, and the "0:00" default covers the failure branch.
- A β @editable belongs to creative_device classes, which have a Details panel in UEFN. A module has no placed instance to edit, so the field is rejected. The shared-module pattern is: devices own the @editable references and hand them to module helpers as parameters.
- B β Quotient[754, 60] is the integer quotient 12, and Mod[754, 60] is the remainder 34. Seconds (34) is not below 10, so the else branch formats "{Minutes}:{Seconds}" = "12:34" β exactly the example in the code comment.
Recap
One step closer to Coastal Race β RaceHelpers module (FormatTime, SafeIndex, Logging) imported by every subsequent lesson (content/verse/shared-helper-module.json compile-PASSED; backed by using-a-module + function-as-helper, both PASS)
Sources
- /guides/shared-helper-module
Lesson 9: Async 101: Sleep, loop, and the countdown
Objectives
- Student can write <suspends> functions using Sleep() in loops to build countdowns and repeating timers without blocking the game.
Student can write <suspends> functions using Sleep() in loops to build countdowns and repeating timers without blocking the game.
π Builds on: south-shores: hud_message_device for the countdown display
π§© Your capstone piece: The 3-2-1-GO race countdown broadcast to all racers (companions loop-with-sleep, sleep-timer, sug-implementing-asynchronous-timers-with-sleep all PASS)
Quiz
- Which `timer_device` event fires when the countdown reaches zero without being manually completed?
- A. SuccessEvent
- B. FailureEvent
- C. StartUrgencyModeEvent
- D. ResetEvent
- Your handler is subscribed to `SuccessEvent`. What is the correct function signature?
- A. OnSuccess(Agent : agent) : void
- B. OnSuccess(Agent : ?agent) : void
- C. OnSuccess() : void
- D. OnSuccess(Agent : player) : void
- You want to add 20 seconds to a player's running countdown. Which method do you call?
- A. RaceTimer.AddTime(20.0, Agent)
- B. RaceTimer.SetActiveDuration(RaceTimer.GetActiveDuration(Agent) + 20.0, Agent)
- C. RaceTimer.Resume(Agent)
- D. RaceTimer.ResetForAll(Agent)
- You call `RaceTimer.Disable()` and then `RaceTimer.Start()`. What happens?
- A. The timer starts normally
- B. The timer starts but immediately pauses
- C. The Start call is silently ignored because the device is disabled
- D. A compile error is thrown
- After calling `RaceTimer.ResetForAll()`, what is the timer's state?
- A. Running from the base duration
- B. Stopped at the base duration, waiting for a new Start call
- C. Paused at whatever time was remaining
- D. Completed and firing SuccessEvent
Answer key
- B β `FailureEvent` is signaled when the timer runs out naturally. `SuccessEvent` fires when `Complete()` or `CompleteForAll()` is called explicitly. There is no `ResetEvent`.
- B β `SuccessEvent` is typed `listenable(?agent)`, so the handler must accept `?agent` (optional agent). You then unwrap it with `if (A := Agent?):` before using agent-specific APIs.
- B β `SetActiveDuration` sets the remaining time to a specific value. To add time, read the current value with `GetActiveDuration`, add your bonus, and pass the result back to `SetActiveDuration`.
- C β While a device is disabled it does not receive signals, including method calls like `Start`. You must call `Enable()` before `Start()` for the timer to begin.
- B β `ResetForAll` rewinds the timer to its configured base duration AND stops it. It does NOT restart automatically β you must call `Start()` or `StartForAll()` again to begin a new countdown.
Recap
One step closer to Coastal Race β The 3-2-1-GO race countdown broadcast to all racers (companions loop-with-sleep, sleep-timer, sug-implementing-asynchronous-timers-with-sleep all PASS)
Sources
- /guides/countdown-timer-loop
Lesson 10: Marooned Town: fire-and-forget with spawn
Objectives
- Student can launch independent async tasks with spawn{} and knows when spawn is right vs. awaiting inline.
Student can launch independent async tasks with spawn{} and knows when spawn is right vs. awaiting inline.
π§© Your capstone piece: One spawned per-racer monitor task (position/checkpoint watcher) per player at race start
Quiz
- Why must `UpdatePlayerUIAsync` be marked `<suspends>`?
- A. Because it is editable
- B. Because it calls `Sleep`, which yields execution
- C. Because it returns a logic
- D. Because all event handlers must be `<suspends>`
- How is `GetRandomFloat` correctly called given its signature `GetRandomFloat(Low:float, High:float)<transacts>:float`?
- A. With brackets: `GetRandomFloat[1.0, 3.0]` because it's fallible
- B. With parens, storing the returned float: `Delay := GetRandomFloat(1.0, 3.0)`
- C. Inside an `if` condition only
- D. By spawning it
- What does the `spawn` expression do?
- A. Freezes the game until the task completes
- B. Starts an async task that lives independently of its creating scope
- C. Creates a new player
- D. Forces a function to be `<decides>`
- Why does the `OnTriggered` handler use `spawn` instead of calling the async function directly?
- A. To make the code shorter
- B. So the handler returns immediately and doesn't block while the task sleeps
- C. Because trigger handlers cannot call functions
- D. To roll back the trigger
- Which call actually performs the visible UI update in this device?
- A. `Print(...)`
- B. `Sleep(Delay)`
- C. `SuccessMessage.Show(TriggeringAgent)`
- D. `spawn{...}`
Answer key
- B β Any function that awaits or calls a `<suspends>` API like `Sleep` must itself be `<suspends>`.
- B β It is `<transacts>`, not `<decides>`, and returns a plain `float`, so call it with `()` and capture the value.
- B β `spawn` is Verse's unstructured concurrency escape hatch β it launches an async expression that runs independently.
- B β `OnTriggered` is not `<suspends>`; spawning the slow work lets the handler return instantly while the task runs in the background.
- C β `hud_message_device.Show(Agent)` displays the configured HUD message to that player β the concrete UI update.
Recap
One step closer to Coastal Race β One spawned per-racer monitor task (position/checkpoint watcher) per player at race start
Sources
- /guides/sug-launching-asynchronous-logic-with-spawn-in-verse
Lesson 11: All Boats Together: sync waits for everyone
Objectives
- Student can run tasks in parallel with sync{} and use all results once every branch completes.
Student can run tasks in parallel with sync{} and use all results once every branch completes.
π§© Your capstone piece: The synced start gate: all racers' ready-checks complete before GO (companion sync-await-all + sync, both PASS)
Quiz
- What does the `sync` expression return when all its tasks complete?
- A. Nothing (void)
- B. A tuple of each subexpression's result, in order
- C. Only the first task's result
- D. A logic value indicating success
- If three tasks under `sync` sleep for 3.0, 2.0, and 1.0 seconds, roughly how long does the block take?
- A. 6.0 seconds
- B. 1.0 second
- C. 3.0 seconds
- D. 2.0 seconds
- Why must `sync` be used inside a `<suspends>` context like `OnBegin`?
- A. Because it modifies persistent state
- B. Because it awaits async work that may span simulation updates
- C. Because it fails as control flow
- D. Because it returns a logic value
- How do you access the second result from `Results := sync: ...`?
- A. Results[1]
- B. Results.1
- C. Results(1)
- D. Results.Get(1)
- What is the defining behavior of `sync` regarding completion?
- A. It continues as soon as the first task finishes
- B. It waits for ALL subexpressions to complete before continuing
- C. It cancels slower tasks once one finishes
- D. It runs tasks one at a time
Answer key
- B β `sync` returns a tuple containing each subexpression's result in source order, accessible via Results(0), Results(1), etc.
- C β Tasks run in parallel, so total time equals the slowest one (3.0s), not the sum (6.0s).
- B β `sync` runs async expressions that can take time across simulation updates, so it requires an async (<suspends>) context.
- C β Tuple elements are accessed with parentheses and a zero-based index, so Results(1) is the second element.
- B β `sync` is a barrier: it only proceeds after every single subexpression has completed.
Recap
One step closer to Coastal Race β The synced start gate: all racers' ready-checks complete before GO (companion sync-await-all + sync, both PASS)
Sources
- /guides/sug-parallel-execution-with-sync-in-verse
Lesson 12: Kraken's Cove: race β first task wins, losers cancelled
Objectives
- Student can use race{} to run competing tasks where the first to finish cancels the rest β the timeout pattern.
Student can use race{} to run competing tasks where the first to finish cancels the rest β the timeout pattern.
π§© Your capstone piece: Storm-vs-finish-line: race{AwaitAllFinished(), StormTimer()} ends the race either way (FAILS compile 1 err β rewrite first; central to zone theme)
Quiz
- What does a `race` expression do when its fastest branch completes?
- A. Runs the remaining branches to completion
- B. Returns the winner's value and cancels the other branches
- C. Restarts all branches
- D. Returns an array of all branch results
- Why must `GetPlayerUI[Player]` be called inside an `if (...)` condition?
- A. It is async and needs <suspends>
- B. It is fallible (uses [] brackets) and may only run in a failure context
- C. It modifies persistent state
- D. It returns a logic that must be toggled
- Why is each race branch wrapped in a `block:` ending in a string?
- A. To make the branch async
- B. So the block's final expression becomes the branch's value used as the race result
- C. Because race requires exactly two branches
- D. To cancel the branch early
- What type must `text_block.SetText` receive?
- A. string
- B. message
- C. text_block
- D. logic
- What does the `<localizes>` specifier on `StringToMessage` enable it to return?
- A. A logic value
- B. A message from a string, suitable for UI text
- C. A canvas widget
- D. An awaitable event
Answer key
- B β `race` takes the first branch to finish, uses its result, and cancels every losing branch automatically.
- B β The `[]` brackets mark it fallible, so it can only appear in a failure context such as an `if` condition.
- B β A block evaluates to its last expression, so ending each branch in a string label lets `race` return which trigger won.
- B β `SetText` takes a `message`, which is why we use a `<localizes>` helper to convert our string.
- B β `<localizes>` functions produce `message` values, letting us turn a plain string into text the UI accepts.
Recap
One step closer to Coastal Race β Storm-vs-finish-line: race{AwaitAllFinished(), StormTimer()} ends the race either way (FAILS compile 1 err β rewrite first; central to zone theme)
Sources
- /guides/sug-implementing-race-expressions-for-concurrent-verse-tasks
Lesson 13: First Across the Line: rush keeps the losers running
Objectives
- Student can choose between race and rush β rush returns the first result but lets remaining tasks continue.
Student can choose between race and rush β rush returns the first result but lets remaining tasks continue.
π§© Your capstone piece: Winner detection: rush over per-racer finish awaits crowns first place while others keep racing for podium spots
Quiz
- What distinguishes `rush` from `race` in Verse?
- A. rush cancels slower subexpressions; race does not
- B. rush continues when the fastest finishes but does NOT cancel slower tasks
- C. rush runs expressions sequentially
- D. rush can only contain one subexpression
- How many async subexpressions does a `rush` block require?
- A. Exactly one
- B. Two or more
- C. Exactly three
- D. Any number including zero
- Why must `rush` appear inside a `<suspends>` context such as `OnBegin`?
- A. Because it allocates memory
- B. Because its subexpressions are async and need a suspendable context
- C. Because it returns a logic value
- D. Because creative_device requires it
- What is the correct way to obtain a player's HUD in the grounding?
- A. GetPlayerUI(Player)
- B. Player.GetPlayerUI()
- C. GetPlayerUI[Player] inside an if
- D. Player.GetCanvas()
- Which method adds a widget to a player's HUD?
- A. PlayerUI.AddWidget(Widget)
- B. PlayerUI.GetCanvas()
- C. PlayerUI.Show(Widget)
- D. Widget.Attach(PlayerUI)
Answer key
- B β `rush` proceeds when the fastest subexpression completes but lets the remaining tasks keep running. `race` cancels the losers.
- B β Like sync and race, rush runs a block of two or more async expressions concurrently.
- B β rush schedules async work, so it can only run where suspension is allowed β a `<suspends>` function.
- C β `GetPlayerUI` is `<transacts><decides>`, so it's called with brackets in a failure context and the result is bound.
- A β `AddWidget<native><public>(Widget:widget):void` adds the widget using default slot configuration.
Recap
One step closer to Coastal Race β Winner detection: rush over per-racer finish awaits crowns first place while others keep racing for podium spots
Sources
- /guides/sug-implementing-concurrent-logic-with-rush-expressions-in-verse
Lesson 14: The Timing Toolbox: spawn/sync/race/loop together
Objectives
- Student can combine all four concurrency primitives into one game-timing structure and pick the right tool per job.
Student can combine all four concurrency primitives into one game-timing structure and pick the right tool per job.
π§© Your capstone piece: The full race-loop skeleton: loop rounds, sync starts, race timeouts, spawn monitors (FAILS compile 5 err β the zone's worst-broken load-bearing article; intermediate sibling verse-concurrency-async-race-state-machines PASSES as backup)
Quiz
- Which keyword starts an async task in the background so the current function can return immediately?
- A. race
- B. sync
- C. spawn
- D. loop
- What does a `race` expression do when one of its branches finishes?
- A. Waits for all branches to finish
- B. Cancels the remaining branches
- C. Restarts the slower branches
- D. Runs them sequentially instead
- Why must `Sleep`, `race`, and `spawn`-launched awaits live inside `OnBegin`?
- A. Because OnBegin is the only public function
- B. Because they need a <suspends> (async) context
- C. Because OnBegin runs faster
- D. Because @editable requires it
- How do you correctly reassign a `var` in Verse?
- A. Remaining -= 1
- B. Remaining--
- C. set Remaining = Remaining - 1
- D. Remaining = Remaining - 1
- Why is `GetPlayerUI[Agent]` called inside an `if` rather than as a plain statement?
- A. It always returns void
- B. It is fallible (uses []) and needs a failure context
- C. It is a device event
- D. It requires a loop
Answer key
- C β `spawn` launches an asynchronous expression and lets the calling code continue right away.
- B β `race` keeps the result of the first branch to finish and cancels the others.
- B β These are async operations that require a `<suspends>` context, which `OnBegin<override>()<suspends>` provides.
- C β Verse uses `set X = ...`. There is no `--` or `-=` operator, and bare `X = value` on a var is an error.
- B β The `[]` brackets mean it's a `<decides>`/fallible call, which may only appear in a failure context like an `if` condition.
Recap
One step closer to Coastal Race β The full race-loop skeleton: loop rounds, sync starts, race timeouts, spawn monitors (FAILS compile 5 err β the zone's worst-broken load-bearing article; intermediate sibling verse-concurrency-async-race-state-machines PASSES as backup)
Sources
- /guides/verse-concurrency-spawn-sync-race-and-loop-for-game-timing
Lesson 15: Mermaid Lagoon: await events, never poll
Objectives
- Student can subscribe to and await device/custom events instead of loop-polling state, and can define a custom event(t) signal.
Student can subscribe to and await device/custom events instead of loop-polling state, and can define a custom event(t) signal.
π Builds on: north-jungle: verse-events-custom (event bus)
π§© Your capstone piece: RaceStartedEvent / RacerFinishedEvent custom signals gluing the phases together (content/verse compile-PASSED; companions subscribe-to-event, await-an-event, custom-event-signal all PASS)
Quiz
- You have this poll loop watching a var:
loop:
if (DoorOpened?):
break
Sleep(0.1)
What is the correct event-driven replacement?
- A. Declare DoorOpenedEvent : event() = event(){}, call DoorOpenedEvent.Signal() where the var used to be set, and replace the whole loop with DoorOpenedEvent.Await()
- B. Shrink the Sleep to 0.01 so the loop reacts faster
- C. Replace Sleep(0.1) with Sleep(0.0) so the loop checks every frame
- D. Wrap the loop in spawn{} so it polls on a background task instead
- RacerFinishedEvent is declared as event(agent). What does RacerFinishedEvent.Await() evaluate to?
- A. The agent payload passed to the matching Signal call
- B. A logic value indicating whether the event has ever fired
- C. Nothing - Await() on an event always returns void
- D. A subscription handle you must cancel later
- Why does the finish-line handler unwrap before signalling?
OnFinishLineCrossed(MaybeRacer : ?agent) : void =
if (Racer := MaybeRacer?):
RacerFinishedEvent.Signal(Racer)
- A. trigger_device.TriggeredEvent sends ?agent because a non-agent cause can trip the trigger, but RacerFinishedEvent carries a plain agent - so the optional must be proven before Signal
- B. Signal() can only be called inside an if expression
- C. Unwrapping makes the event fire faster
- D. ?agent and agent are interchangeable; the unwrap is purely stylistic
- In the signal hub, OnBegin does RacerFinishedEvent.Await() while OnRacerFinished is Subscribed to the same event. What is the behavioural difference?
- A. Await resumes its task once, on the NEXT Signal only; Subscribe's handler runs on EVERY Signal from then on
- B. They are identical - Subscribe is just the non-suspending spelling of Await
- C. Subscribe only works on device events, so the custom-event Subscribe is ignored
- D. Await blocks the whole island until the event fires; Subscribe does not
- The signal hub declares RaceStartedEvent : event() = event(){}. Which using-import does the event(t) class require?
- A. using { /Fortnite.com/Devices }
- B. using { /Verse.org/Verse }
- C. using { /UnrealEngine.com/Temporary/Diagnostics }
- D. using { /Verse.org/Simulation }
Answer key
- A β Polling is replaced by signalling: the code that used to `set DoorOpened = true` now calls `Signal()`, and the waiting task replaces its entire loop with a single `Await()`. Shrinking the Sleep or moving the loop to another task still polls β it just changes where the waste happens.
- A β `event(t).Await()` suspends until the next `Signal(Payload)` and returns that payload β so `Winner := RacerFinishedEvent.Await()` binds the finishing agent directly.
- A β `TriggeredEvent` is a `listenable(?agent)` β the payload is optional. Our custom event demands a real `agent`, so the handler unwraps with `if (Racer := MaybeRacer?)` and only signals when a racer actually crossed.
- A β An awaiter is a one-shot: the suspended task resumes on the next `Signal` and moves on (here, catching the FIRST finisher). A subscriber is persistent: its handler runs for every occurrence (here, congratulating EVERY finisher). Both patterns coexist on one event.
- B β The article calls this out in Step 1: `event()` needs `using { /Verse.org/Verse }`. Devices come from /Fortnite.com/Devices, Print from Diagnostics, and Sleep from Simulation β none of those bring in `event(t)`.
Recap
One step closer to Coastal Race β RaceStartedEvent / RacerFinishedEvent custom signals gluing the phases together (content/verse compile-PASSED; companions subscribe-to-event, await-an-event, custom-event-signal all PASS)
Sources
- /guides/prefer-events-over-polling
Lesson 16: Mangrove Channel Gates: trigger_device as checkpoint sensor
Objectives
- Student can wire trigger_device TriggeredEvent into Verse with agent payloads to detect exactly who passed a gate.
Student can wire trigger_device TriggeredEvent into Verse with agent payloads to detect exactly who passed a gate.
π Builds on: center-village: trigger wiring + button-event bridge patterns
π§© Your capstone piece: Physical checkpoint sensors along the race channel feeding CheckpointPassed(Racer, Index)
Quiz
- What type does the trigger_device's TriggeredEvent hand to your subscribed handler?
- A. agent
- B. ?agent (an optional agent)
- C. player
- D. fort_character
- Why must you declare the trigger as an @editable field inside your creative_device class?
- A. To make it run faster
- B. So you can bind it to the placed device and call its methods; a bare Device.Method() fails
- C. Because TriggeredEvent only works on editable fields
- D. It is optional and only for readability
- What value passed to SetMaxTriggerCount means 'no limit'?
- What does GetTriggerCountRemaining() return when the device's max trigger count is unlimited?
- A. The number of players in the game
- B. 20
- C. 0
- D. An error
- Which call fires the device's TriggeredEvent from code while attaching a specific player?
- A. Trigger()
- B. Trigger(Agent)
- C. Enable()
- D. SetTransmitDelay(Agent)
Answer key
- B β TriggeredEvent is listenable(?agent) because the device can also be fired from code with no agent, so you must unwrap the option before using it.
- B β Calling a method on a placed device requires an @editable field you point at the device in the Details panel; otherwise you get 'Unknown identifier'.
- C β 0 indicates unlimited triggers. The argument is clamped between 0 and 20.
- C β Per the API, it returns 0 when GetMaxTriggerCount is unlimited, so don't treat 0 as 'empty' in that configuration.
- B β Trigger(Agent:agent) fires the event and passes that agent along; the agentless Trigger() fires with no agent.
Recap
One step closer to Coastal Race β Physical checkpoint sensors along the race channel feeding CheckpointPassed(Racer, Index)
Sources
Lesson 17: Firefly Cove at Dusk: PlayerAddedEvent and the join lifecycle
Objectives
- Student can handle players joining mid-session (PlayerAddedEvent + GetPlayspace().GetPlayers()) and initialize per-player state.
Student can handle players joining mid-session (PlayerAddedEvent + GetPlayspace().GetPlayers()) and initialize per-player state.
π Builds on: south-shores: player spawn pad basics
π§© Your capstone piece: The race lobby: joiners registered into the Racers roster + assigned a boat dock (content/verse compile-PASSED β pin URL was wrongly flagged broken; sibling detect-player-leave also PASSES for the leave path)
Quiz
- Why must OnBegin ALSO iterate GetPlayspace().GetPlayers() instead of relying on PlayerAddedEvent alone?
- A. PlayerAddedEvent only fires for players who join AFTER the subscription β players already in the playspace when OnBegin runs never trigger it.
- B. GetPlayers() is required to activate PlayerAddedEvent; the event stays dormant until the list is read once.
- C. PlayerAddedEvent fires for AI participants too, so the iteration filters those out.
- D. Iterating GetPlayers() is only needed in multiplayer playtests, never in published islands.
- Which is the correct way to subscribe to the playspace join event?
- A. GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerJoined)
- B. GetPlayspace().PlayerAddedEvent.Subscribe(OnPlayerJoined)
- C. GetPlayspace().Subscribe(PlayerAddedEvent, OnPlayerJoined)
- D. PlayerAddedEvent(GetPlayspace()).Subscribe(OnPlayerJoined)
- In OnBegin, why does the registrar subscribe to PlayerAddedEvent BEFORE sweeping GetPlayers()?
- A. So a player joining between the two steps is caught by the event β the worst case becomes a duplicate registration, which the idempotent RegisterRacer skips.
- B. Because Verse requires all Subscribe calls to appear before any for loop in OnBegin.
- C. Because GetPlayers() returns an empty array until at least one subscription exists.
- D. The order does not matter; both orders behave identically.
- The lobby spawn pad's SpawnedEvent delivers an agent, but the Racers roster stores player values. What is the correct narrowing step?
- A. if (SpawnedPlayer := player[SpawnedAgent]): β a failable cast that succeeds only when the agent is a human player.
- B. SpawnedPlayer := SpawnedAgent as player β an unchecked cast.
- C. SpawnedPlayer := player(SpawnedAgent) β a constructor call.
- D. No narrowing is needed; agent and player are interchangeable types.
- A player joins Firefly Cove five minutes after the match started. Which line does the registrar print first?
- A. "Racer already registered - skipping."
- B. "A new racer drifts into Firefly Cove."
- C. "Welcome to the Coastal Race! Your boat awaits."
- D. Nothing β mid-session joiners are only caught by the OnBegin sweep.
Answer key
- A β The event is not retroactive. Anyone standing on the island before your OnBegin subscribed will never fire PlayerAddedEvent, so per-player initialization must also sweep the current GetPlayers() list. The two halves together cover the full join lifecycle.
- A β PlayerAddedEvent is a function that RETURNS a listenable(player) β signature PlayerAddedEvent<public>():listenable(player) β so it needs the () call before .Subscribe. Device events like SpawnedEvent are plain fields and take .Subscribe directly.
- A β Sweep-then-subscribe leaves a gap where a joiner is missed by BOTH paths. Subscribe-then-sweep means an unlucky joiner might be delivered twice instead β and a duplicate is harmless because RegisterRacer checks DockAssignments before registering.
- A β player[Agent] is Verse's failable type cast: inside a failure context it succeeds for human players and fails for other agents (like AI). That keeps NPCs out of a player-typed roster and map.
- B β A mid-session joiner arrives via the PlayerAddedEvent path: OnPlayerJoined prints "A new racer drifts into Firefly Cove." and then calls RegisterRacer. The welcome banner is a HUD message device, not a Print, and the OnBegin sweep only covers players present before the device started.
Recap
One step closer to Coastal Race β The race lobby: joiners registered into the Racers roster + assigned a boat dock (content/verse compile-PASSED β pin URL was wrongly flagged broken; sibling detect-player-leave also PASSES for the leave path)
Sources
- /guides/detect-player-join
Lesson 18: Hidden Cove Ambush: elimination_result and EliminatedEvent
Objectives
- Student can subscribe to fort_character EliminatedEvent, read elimination_result (who/by-whom), and drive game logic from eliminations.
Student can subscribe to fort_character EliminatedEvent, read elimination_result (who/by-whom), and drive game logic from eliminations.
π§© Your capstone piece: DNF handling: eliminated racers leave the roster, feed the kill-ticker, become spectators (content/verse compile-PASSED; support tracking-player-health-shields + elimination-feed-device PASS; elimination-manager-device FAILS 1 err β fix as supporting ref)
Quiz
- You have an `elimination_result` named `Result` and want to credit the eliminator's score ONLY when a real player dealt the blow. Which code is correct?
- A. if (Killer := Result.EliminatingCharacter?, KillerAgent := Killer.GetAgent[]) { ScoreDevice.Activate(KillerAgent) }
- B. ScoreDevice.Activate(Result.EliminatingCharacter.GetAgent())
- C. if (Result.EliminatingCharacter = true) { ScoreDevice.Activate(Result.EliminatingCharacter) }
- D. ScoreDevice.Activate(Result.EliminatedCharacter.GetAgent[])
- What payload does `fort_character.EliminatedEvent()` deliver to its subscribers?
- A. An `elimination_result` struct carrying `EliminatedCharacter : fort_character` and `EliminatingCharacter : ?fort_character`
- B. The `agent` that was eliminated
- C. A `tuple(fort_character, fort_character)` of victim and killer
- D. Nothing - it is a plain signal with no payload
- Why is `EliminatingCharacter` typed as `?fort_character` (an option) instead of plain `fort_character`?
- A. Because not every elimination has an author - storm damage, fall damage, and other environmental deaths have no eliminating character
- B. Because the eliminator might have left the game before the event fires
- C. Because options are faster to copy than interface references
- D. Because Verse requires all struct fields to be optional
- Your race mode respawns eliminated players for a second lap. Your elimination watcher stops firing for respawned players. Why?
- A. A respawned player gets a NEW fort_character instance - the old subscription is attached to the eliminated one, so you must call GetFortCharacter[] and subscribe again
- B. EliminatedEvent can only ever fire once per game
- C. The playspace cancels all subscriptions when any player is eliminated
- D. Respawned characters are invulnerable and cannot be eliminated
- This line fails to compile: `Character.EliminatedEvent.Subscribe(OnEliminated)`. Why?
- A. Subscribe requires a suspending function as its argument
- B. `EliminatedEvent` is a function, not a property - you must CALL it with `()` to get the listenable: `Character.EliminatedEvent().Subscribe(OnEliminated)`
- C. You can only Subscribe to device events, never character events
- D. EliminatedEvent must be awaited before it can be subscribed
Answer key
- A β `EliminatingCharacter` is `?fort_character` - an option. The `?` postfix unwrap inside a failure context (`if`) succeeds only when an eliminator exists, and `GetAgent[]` is failable too, so both belong in the same `if`. Environmental eliminations (storm, fall damage) leave the option empty and the whole condition fails - no score awarded. Option 3 activates for the VICTIM'S agent, which is the classic scoring bug.
- A β The digest signature is `EliminatedEvent<public>():listenable(elimination_result)`. The `elimination_result` struct (from `/Fortnite.com/Game`) always contains the eliminated `fort_character`, plus an OPTIONAL eliminating character that is false for environmental eliminations.
- A β The digest comment says it directly: `EliminatingCharacter` will be false when the character was eliminated through non-character actions, such as environmental damage. The option type forces you to handle the no-eliminator case at compile time.
- A β A `fort_character` is one incarnation of a player. Elimination ends that incarnation; respawning creates a fresh `fort_character`. Any per-character subscription must be re-established on the new character - a common pattern is a loop that re-fetches `GetFortCharacter[]` after each elimination, or subscribing from a `SpawnedEvent` handler.
- B β The digest signature is `EliminatedEvent<public>():listenable(elimination_result)` - note the `()`. Unlike device events such as a trigger's `TriggeredEvent`, which are plain properties, `EliminatedEvent` is a function that RETURNS a listenable. Call it first, then `Subscribe(...)` or `Await()` the result.
Recap
One step closer to Coastal Race β DNF handling: eliminated racers leave the roster, feed the kill-ticker, become spectators (content/verse compile-PASSED; support tracking-player-health-shields + elimination-feed-device PASS; elimination-manager-device FAILS 1 err β fix as supporting ref)
Sources
- /guides/elimination-event
Lesson 19: Treasure Cove Phases: the game state machine
Objectives
- Student can model game flow as an enum-state machine with async phase functions and event-driven transitions (LobbyβCountdownβRacingβPodiumβReset).
Student can model game flow as an enum-state machine with async phase functions and event-driven transitions (LobbyβCountdownβRacingβPodiumβReset).
π Builds on: south-shores: enum-with-data-pattern (state seed)
π§© Your capstone piece: The Coastal Race master state machine orchestrating every prior piece (content/verse compile-PASSED; verse-concurrency-async-race-state-machines PASSES as the deep companion; sug-implementing-game-state-management-with-verse-classes FAILS 1 err β optional fix)
Quiz
- In the Coastal Race transition table, which event moves Racing β Podium, and which moves Racing β Reset?
- A. RacerFinished (finish line crossed) β Podium; the storm timeout elapsing first β Reset.
- B. AllReady β Podium; RacerFinished β Reset.
- C. The storm timeout β Podium; RacerFinished β Reset.
- D. Both transitions fire on RacerFinished β the machine picks randomly.
- Why does each phase function return a `race_phase` instead of setting `Phase` itself?
- A. So the dispatcher loop is the ONLY place the state changes β one `set Phase = ...` line, making every transition traceable.
- B. Because Verse forbids `set` inside `<suspends>` functions.
- C. Because enums cannot be assigned outside `OnBegin`.
- D. It is purely stylistic; scattering `set Phase` across handlers works just as safely.
- A latecomer presses the ready button while the Racing phase is running. What happens?
- A. Nothing β AllReady.Signal() fires, but no task is Awaiting it during Racing, so the signal is simply dropped.
- B. The machine instantly transitions back to Countdown.
- C. The game crashes because Signal() without an Await is a runtime error.
- D. The signal is queued and consumed the next time the Lobby phase runs.
- In Pattern 2, why must you `set LastWinner = MaybeAgent` BEFORE calling `RacerFinished.Signal()`?
- A. The awaiting phase resumes as soon as Signal() fires β signaling first would let the podium phase read stale (or empty) winner data.
- B. Verse requires all `set` statements to precede method calls in a function body.
- C. Signal() clears every var on the device, so the write would be lost.
- D. The order does not matter; Await always waits one extra frame before resuming.
- Predict the output: when RunCountdown() runs, what does the log show?
- A. [Cove] 3... then [Cove] 2... then [Cove] 1... then [Cove] GO! β it prints the count, sleeps one second, decrements, and breaks when the count reaches 0.
- B. [Cove] GO! immediately, then 3... 2... 1... afterwards.
- C. [Cove] 3... [Cove] 2... [Cove] 1... [Cove] 0... [Cove] GO! β it prints zero before breaking.
- D. Nothing β Countdown exits on an event signal, not a timer.
Answer key
- A β RunRacing races two arms: one Awaits RacerFinished (signaled when the finish-line trigger fires) and returns the tag for Podium; the other Sleeps for StormTimeoutSeconds and returns the tag for Reset. Whichever completes first decides the next phase; the losing arm is cancelled.
- A β Funneling every transition through the dispatcher's single `set Phase = ...` means there is exactly one place state changes. Phase functions stay pure 'run, wait, recommend next phase' units β which is what makes the machine easy to debug and extend.
- A β Custom event() signals only wake tasks that are currently Awaiting. Since only RunLobby Awaits AllReady, an out-of-phase press evaporates harmlessly β the state machine gets transition guards for free, with no `if` checks in the handlers.
- A β Signal() wakes the Awaiting task immediately. The payload var is the data riding along with the transition, so it must be written before the transition fires β stash first, signal second.
- A β RunCountdown starts Count at 3, and each loop iteration prints the current Count, Sleeps 1.0 second, decrements, and breaks once Count <= 0. Zero is never printed because the break happens before another print, and 'GO!' prints only after the loop exits.
Recap
One step closer to Coastal Race β The Coastal Race master state machine orchestrating every prior piece (content/verse compile-PASSED; verse-concurrency-async-race-state-machines PASSES as the deep companion; sug-implementing-game-state-management-with-verse-classes FAILS 1 err β optional fix)
Sources
- /guides/simple-game-state-machine
Lesson 20: The Rusty Barnacle: Inkbeard's NPC behaviour state machine
Objectives
- Student can author npc_behavior Verse with an internal state machine (idle/patrol/chase) and attach it to an NPC spawner.
Student can author npc_behavior Verse with an internal state machine (idle/patrol/chase) and attach it to an NPC spawner.
π Builds on: west-coves L19 state machine + north-jungle classes
π§© Your capstone piece: The Kraken hazard NPC: patrols the channel, chases and eliminates straggler racers (support: npc-behavior, npc-spawner-device, verse-npc-custom-behavior, verse-npc-guard-patrol all PASS; spawn-npc FAILS 1 err)
Quiz
- You want Captain Crow's alert timer to last 20 seconds for a specific player who stepped on the dock. Which call is correct?
- A. AlertTimer.SetActiveDuration(20.0, A) where A is the unwrapped agent
- B. AlertTimer.SetActiveDuration(20, A) where A is the unwrapped agent
- C. AlertTimer.Start(20.0)
- D. AlertTimer.SetMaxDuration(20.0)
- Why must you call AlertTimer.Enable() before AlertTimer.Start()?
- A. Enable() allocates memory for the timer object
- B. Start() silently does nothing on a disabled timer
- C. Enable() sets the timer duration
- D. Start() requires an agent parameter when the timer is disabled
- What does passing 0 to DockTrigger.SetMaxTriggerCount(0) do?
- A. Disables the trigger immediately
- B. Sets the trigger to fire exactly once
- C. Removes the trigger limit so it can fire unlimited times
- D. Resets the trigger count to its default value
- You subclass npc_behavior and call Focus.MaintainFocus(LagoonLocation) directly in OnBegin without a race block. What happens?
- A. The NPC looks at the location for one tick, then continues
- B. The NPC's OnBegin suspends at that line forever, blocking all subsequent logic
- C. MaintainFocus returns immediately because it has no <suspends> effect
- D. The compiler rejects the call because npc_behavior cannot use focus_interface
- StartUrgencyModeEvent on timer_device fires when:
- A. The timer is first enabled
- B. The timer is paused
- C. The timer enters its configured urgency (last-seconds warning) window
- D. The timer's SuccessEvent has already fired
Answer key
- A β SetActiveDuration takes a float and an agent. You must unwrap the ?agent first (if A := MaybeAgent?:) and pass 20.0 (float), not 20 (int), since Verse does not auto-convert int to float.
- B β Calling Start() on a disabled timer_device silently fails β the timer never begins. You must Enable() the device first to allow it to receive signals.
- C β Per the API docs, 0 indicates no limit on trigger count β the trigger can fire unlimited times, which is what you want for a repeating patrol cycle.
- B β MaintainFocus has the <suspends> effect and never returns on its own. Calling it bare in OnBegin freezes the coroutine at that line indefinitely. Always wrap it in a race block with a Sleep or event that can cancel it.
- C β StartUrgencyModeEvent signals when the timer enters Urgency Mode β the configurable last-seconds warning window set in the UEFN device properties. It fires before SuccessEvent or FailureEvent.
Recap
One step closer to Coastal Race β The Kraken hazard NPC: patrols the channel, chases and eliminates straggler racers (support: npc-behavior, npc-spawner-device, verse-npc-custom-behavior, verse-npc-guard-patrol all PASS; spawn-npc FAILS 1 err)
Sources
- /guides/state-machine-with-npc-behavior
Lesson 21: Crystal Grotto Lens: first-person camera for spectators
Objectives
- Student can push/pop a gameplay_camera_first_person_device onto an agent's camera stack from Verse to change perspective at runtime.
Student can push/pop a gameplay_camera_first_person_device onto an agent's camera stack from Verse to change perspective at runtime.
π§© Your capstone piece: Eliminated racers get a first-person spectator cam following the race leader; camera arc step toward Sequencer/control-rig in later zones
Quiz
- What method pushes the first-person camera onto a single specific player's camera stack?
- A. AddTo(Agent)
- B. AddToAll()
- C. Enable()
- D. RemoveFrom(Agent)
- A `trigger_device.TriggeredEvent` handler receives which parameter type?
- A. agent
- B. ?agent
- C. player
- D. void
- You want to prevent the first-person camera from being activated at all during a cutscene. What is the correct sequence of calls?
- A. Disable(), then RemoveFromAll()
- B. RemoveFromAll(), then Disable()
- C. RemoveFrom(Agent), then AddToAll()
- D. Enable(), then RemoveFromAll()
- After calling `RemoveFrom(Agent)`, what happens to that player's view?
- A. The screen goes black until a new camera is manually assigned
- B. The engine restores the next camera down in that player's camera stack
- C. All cameras are removed and the player sees the default sky
- D. The device must call Enable() again before any camera works
- Which statement about `gameplay_camera_first_person_device` events is correct?
- A. It fires a CameraActivatedEvent whenever AddTo is called
- B. It fires a PlayerEnteredFPSEvent you can subscribe to
- C. It has no events β it is purely imperative and you drive it from other devices' events
- D. It inherits TriggeredEvent from gameplay_camera_device
Answer key
- A β `AddTo(Agent)` targets one agent and pushes the camera to the top of that player's stack, making it their active camera. `AddToAll()` does the same for every player at once.
- B β `TriggeredEvent` is a `listenable(?agent)`, so the handler receives an optional agent `?agent`. You must unwrap it with `if (Agent := MaybeAgent?):` before passing it to camera methods.
- B β You should call `RemoveFromAll()` first to cleanly pop the camera from any player stacks it's currently on, then `Disable()` to lock the device so nothing can push it again.
- B β `RemoveFrom` pops this device from the stack and the engine automatically makes the next camera in the stack active β typically the default third-person camera. No manual reassignment is needed.
- C β `gameplay_camera_first_person_device` exposes no events of its own. You react to events from other devices (triggers, buttons, timers) and call the camera's methods (`AddTo`, `RemoveFrom`, etc.) in those handlers.
Recap
One step closer to Coastal Race β Eliminated racers get a first-person spectator cam following the race leader; camera arc step toward Sequencer/control-rig in later zones
Sources
- /guides/gameplay-camera-first-person-device
Lesson 22: Checkpoint Gate Prefab: async behaviour in Scene Graph components
Objectives
- Student can write a custom component whose OnSimulate runs async logic (pulse animation + trigger await) and package the gate entity as a reusable prefab.
Student can write a custom component whose OnSimulate runs async logic (pulse animation + trigger await) and package the gate entity as a reusable prefab.
π Builds on: south-shores/the-deeps scene-graph fundamentals: entities-components, scene-graph-add-component
π§© Your capstone piece: The CheckpointGate prefab (glow pulse, pass-through detection) instanced along the course β and exported for the-deeps to import (FAILS compile 5 err β rewrite; carries the whole async arc into components)
Quiz
- In the Scene Graph, what is an entity best described as?
- A. A component that always glows
- B. A container that holds components
- C. A player's UI widget
- D. A replacement for Verse
- Why is GetPlayerUI called with square brackets in the code?
- A. It concatenates strings
- B. It is a <suspends> function
- C. It is fallible and must sit in a failure context like an if
- D. Brackets are required for all functions
- What happens to a child entity when you move its parent in the Scene Graph?
- A. Nothing, they are independent
- B. The child moves with the parent
- C. The child is deleted
- D. The parent is deleted
- Which type is used for the LanternOn flag?
- A. bool
- B. logic
- C. int
- D. string
- How do you correctly reassign the var LanternOn to true?
- A. LanternOn = true
- B. set LanternOn = true
- C. LanternOn := true
- D. LanternOn += true
Answer key
- B β An entity is just a container; components (mesh, light, etc.) give it its powers.
- C β GetPlayerUI[Player] is fallible, so it is bound inside an `if (UI := GetPlayerUI[Player])`.
- B β Parenting means moving the parent drags every child along, like a LEGO tower.
- B β Verse's boolean type is `logic` (true/false), never `bool`.
- B β Mutable variables are reassigned with `set X = value`.
Recap
One step closer to Coastal Race β The CheckpointGate prefab (glow pulse, pass-through detection) instanced along the course β and exported for the-deeps to import (FAILS compile 5 err β rewrite; carries the whole async arc into components)
Sources
- /guides/attaching-components-to-entities-in-the-verse-scene-graph
Lesson 23: CAPSTONE β Coastal Race: ship it
Objectives
- Student assembles all 22 lessons into a complete playable boat-race mode with lobby, synced start, checkpoints, storm timeout, eliminations, kraken NPC, podium, and persistent win tracking.
Student assembles all 22 lessons into a complete playable boat-race mode with lobby, synced start, checkpoints, storm timeout, eliminations, kraken NPC, podium, and persistent win tracking.
π Builds on: north-jungle modules, south-shores HUD module + SpawnProp, center-village round-logic pattern, east-volcano persistable [player]int pattern
π§© Your capstone piece: The whole minigame (multi-step project article + data/learning-paths/path-west-coves.json record; also fix vehicle-spawner-boat-device FAIL 1 err β the boats! β and lean on race-checkpoint-device + race-manager-device + timer-device, all PASS, as device-track sidebars)
Quiz
- Your build-review checklist maps each Coastal Race mechanic to the lesson that taught it. Which row is WRONG?
- A. Storm timeout β race-expressions: race{} cancels the losing branch
- B. Eliminated racers spectate β elimination-event + first-person-spectator-camera
- C. Persistent career wins β countdown-timer-loop: Sleep() loops keep the count alive
- D. Ordered gates β trigger-device-checkpoints + failure contexts for validation
- The boat dock code subscribes OnBoatSpawned(Boat:fort_vehicle) to SpawnedEvent. Which using{} line is required to make it compile, and what error do you get without it?
- A. using { /Fortnite.com/Vehicles } β without it: Unknown identifier `fort_vehicle`
- B. using { /Fortnite.com/Devices } β without it: Unknown identifier `SpawnedEvent`
- C. using { /Verse.org/Vehicles } β without it: Ambiguous identifier `fort_vehicle`
- D. No extra import β fort_vehicle ships with /Fortnite.com/Devices
- In DoRacing, the code runs race: { AwaitAllFinished(), StormCountdown() }. Every racer crosses the finish line before the storm timeout elapses. What happens?
- A. AwaitAllFinished completes and StormCountdown is cancelled mid-Sleep β the round ends immediately
- B. Both branches always run to completion; the storm message still shows later
- C. StormCountdown wins because Sleep-based branches have scheduler priority
- D. The race expression restarts StormCountdown for the next round automatically
- Which declaration makes each player's win count survive across game sessions?
- A. A module-scoped `var CoastalRaceWins:weak_map(player, int) = map{}`
- B. A `var Wins:[player]int = map{}` member inside coastal_race_device
- C. A local `var Wins:int = 0` inside DoPodium
- D. The RaceConfig module constant `Wins<public>:int = 0`
- A fifth player is in the lobby but the pier only has four Boat Spawners in BoatDocks. During DoCountdown, what happens to racer number five?
- A. The failable `BoatDocks[Index]` inside the if simply fails for index 4 β no boat, no crash; they wait for the next round
- B. The script crashes with an index-out-of-range runtime error
- C. The loop wraps around and assigns them to BoatDocks[0], sharing a boat
- D. DoCountdown blocks forever waiting for a fifth dock to appear
Answer key
- C β Persistence has nothing to do with Sleep loops β career wins survive sessions because `CoastalRaceWins` is a module-scoped `var weak_map(player, int)`, the persistable pattern from East Volcano's tycoon-state lesson. The other three rows map correctly.
- A β `vehicle_spawner_boat_device` itself comes from /Fortnite.com/Devices, but the `fort_vehicle` type that `SpawnedEvent` sends lives in /Fortnite.com/Vehicles. Omitting that using{} produces `Script error 3506: Unknown identifier 'fort_vehicle'` β the classic boat-race compile trap.
- A β race{} runs its branches concurrently and cancels the rest the moment one completes. When the last finisher makes AwaitAllFinished return, the storm branch is cancelled inside its Sleep β the storm announcement never fires. If the timeout elapsed first, the finish-await would be the one cancelled.
- A β UEFN persists module-scoped var weak_map(player, t) declarations per player, per island, across sessions β int is a persistable type, so the map survives. Class members and locals reset every session, and module constants are immutable shared config, not per-player state.
- A β Array indexing in Verse is failable. `if (Dock := BoatDocks[Index])` fails safely when Index is beyond the array, so the extra racer just gets no boat this round β exactly the article's 'a fifth racer at a four-dock pier simply waits' behavior.
Recap
One step closer to Coastal Race β The whole minigame (multi-step project article + data/learning-paths/path-west-coves.json record; also fix vehicle-spawner-boat-device FAIL 1 err β the boats! β and lean on race-checkpoint-device + race-manager-device + timer-device, all PASS, as device-track sidebars)
Sources
- /guides/west-coves-coastal-race
Generated by Verse Island on 2026-07-04.