๐ The Deeps
Teen
21 lessons ยท
~168 min ยท Generated 2026-07-04
The REAL UEFN island for the-deeps needs: TERRAIN/WORLD โ underwater seafloor terrain with a custom-landscape water body (see create-a-body-of-water doc), the marble Athenaeum exterior, and the inner library wing built on a separate World Partition DATA LAYER (runtime-activated);
Learning Outcomes
- Athenaeum Depths (feat. the Sunken Dive Round)
Lesson 1: Read the Ancient Texts: Verse Naming Conventions
Objectives
- Student can read and write professional Verse identifiers (PascalCase types, snake_case classes, descriptive names) so the deep-zone codebases stay legible.
Student can read and write professional Verse identifiers (PascalCase types, snake_case classes, descriptive names) so the deep-zone codebases stay legible.
๐งฉ Your capstone piece: Code style contract for every module in both capstone projects
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 Athenaeum Depths (feat. the Sunken Dive Round) โ Code style contract for every module in both capstone projects
Sources
- /guides/naming-conventions
Lesson 2: Relic Ledger: Dynamic Arrays (Add/Remove)
Objectives
- Student can grow, shrink and rebuild Verse arrays immutably (array + element, RemoveElement, Slice) to track a changing set of relics.
Student can grow, shrink and rebuild Verse arrays immutably (array + element, RemoveElement, Slice) to track a changing set of relics.
๐ Builds on: north-jungle sum-an-array / verse arrays fundamentals
๐งฉ Your capstone piece: The RelicLedger: the array of un-banked relic IDs the dive round drains
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 Athenaeum Depths (feat. the Sunken Dive Round) โ The RelicLedger: the array of un-banked relic IDs the dive round drains
Sources
Lesson 3: Listen to the Deep: Events Over Polling
Objectives
- Student can replace loop+Sleep polling with Subscribe/Await reactive patterns and explain why event-driven code scales.
Student can replace loop+Sleep polling with Subscribe/Await reactive patterns and explain why event-driven code scales.
๐ Builds on: north-jungle verse-events-custom (custom event bus module)
๐งฉ Your capstone piece: The reactive backbone: every capstone system reacts to events, nothing polls
Quiz
- You inherit this loop: `loop: Sleep(0.1); if (RelicsLooted >= RelicsNeeded) { break }` where a trigger handler increments RelicsLooted. What is the correct event-driven refactor, and what is the frame-cost difference?
- A. Check the threshold inside the trigger's Subscribe handler (or Await a custom event Signaled there); the polling task wakes ~10x/sec even when nothing changes, while the reactive version costs zero between firings and reacts the same frame the count changes.
- B. Shorten the Sleep to 0.01 so the loop reacts faster; the frame cost is the same either way.
- C. Move the loop into a spawned task; spawned loops are free, so the frame cost disappears.
- D. Replace Sleep with Await(0.1); Await is just a cheaper Sleep with identical semantics.
- What is the key difference between `MyEvent.Subscribe(Handler)` and `MyEvent.Await()`?
- A. Subscribe registers a handler that runs on EVERY future firing without suspending the caller; Await suspends the current task until the NEXT single firing, then execution continues in-line.
- B. Await runs the handler on every firing; Subscribe only fires once.
- C. Subscribe only works on device events; Await only works on custom events.
- D. They are identical โ Await is just the older name for Subscribe.
- Your handler for `trigger_device.TriggeredEvent` receives `MaybeDiver : ?agent`. Why does the backbone unwrap it with `if (Diver := MaybeDiver?)` before calling `deep_events.RelicLootedEvent.Signal(Diver)`?
- A. TriggeredEvent's payload is an OPTIONAL agent (the device can fire with no instigator), but the bus event carries a guaranteed `agent` โ unwrapping at the producer means every downstream subscriber skips the nil-check.
- B. Because Signal can only be called inside an if expression.
- C. Because ?agent must always be converted to player before use.
- D. The unwrap is optional style; Signal(MaybeDiver) would compile identically.
- The vault ledger subscribes to `RelicLootedEvent`, which the pressure plates Signal. Next week you add a HUD counter and a sound cue that also react to looting. Why does the event bus make this scale?
- A. New consumers just Subscribe to the same event โ the plates' producer code is untouched, and each new system adds zero per-frame cost because nothing new polls.
- B. You must edit each plate to call the HUD and sound systems directly, but the bus makes those edits shorter.
- C. The bus batches all handlers into one frame per second, capping total cost.
- D. It doesn't scale โ each new subscriber adds another polling loop internally.
- In `reactive_backbone_device`, what does `VaultOpenedEvent.Await()` do to the OnBegin task while nothing has happened yet?
- A. It suspends the task at zero per-frame cost until VaultOpenedEvent.Signal is called, then resumes in-line with the payload.
- B. It blocks the whole device's Tick loop until the door opens, spending one Tick per frame.
- C. It polls VaultOpenedEvent's internal flag once per frame until true.
- D. It immediately returns false and the task moves on without waiting.
Answer key
- A โ The threshold can only flip at the moment RelicsLooted changes โ inside the handler. Checking it there (or Signaling a custom event that another task Awaits) removes the wake-check-sleep cycle entirely: the polling loop does ~36,000 pointless wakes per hour and still reacts up to one interval late, while the reactive version does zero work between events and reacts immediately.
- A โ Subscribe is fire-and-forget: the handler runs each time the event fires, and you get a cancelable back to unsubscribe later. Await is for sequencing inside a <suspends> context: the task freezes at zero cost, wakes on the next firing with the payload, and continues. Loop an Await when you want every firing with suspending reaction code.
- A โ trigger_device.TriggeredEvent is listenable(?agent) โ e.g. a transmitted trigger has no agent. The bus is declared event(agent), so the producer unwraps once and every consumer downstream receives a guaranteed agent. Passing the option straight through would not type-check against event(agent).
- A โ This is the decoupling you built in the North Jungle bus: producers Signal without knowing who listens, so adding the HUD, SFX, or the capstone's swimming relic prefabs is purely additive โ one Subscribe each, no producer changes, and no idle cost since subscribers only run when the event actually fires.
- A โ Await suspends the calling <suspends> task until the event next fires โ no per-frame work happens while suspended. That is the whole point of the reactive backbone: OnBegin does nothing until RelicLedgerLoop signals VaultOpenedEvent.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The reactive backbone: every capstone system reacts to events, nothing polls
Sources
- /guides/deeps-events-over-polling
Lesson 4: Pressure Plates of the Athenaeum: trigger_device Deep Dive
Objectives
- Student can wire trigger_device TriggeredEvent from Verse with agent payloads, enable/disable at runtime, and chain triggers into sequences.
Student can wire trigger_device TriggeredEvent from Verse with agent payloads, enable/disable at runtime, and chain triggers into sequences.
๐ Builds on: center-village town-plaza trigger wiring lesson (editor-side channels)
๐งฉ Your capstone piece: Vault pressure plates that pop relic chests open during the dive round
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 Athenaeum Depths (feat. the Sunken Dive Round) โ Vault pressure plates that pop relic chests open during the dive round
Sources
Lesson 5: Lost at Sea: detect_player_leave Lifecycle Cleanup
Objectives
- Student can subscribe to the player-removed lifecycle event and safely clean per-player map/array state mid-round.
Student can subscribe to the player-removed lifecycle event and safely clean per-player map/array state mid-round.
๐ Builds on: west-coves option-safe lookups (return-an-option) for map removal
๐งฉ Your capstone piece: Mid-round disconnect handling so a leaver's relics return to the ledger
Quiz
- Bug-hunt: a scoring pass crashes after a player leaves because `BankedCounts` still holds their entry and downstream code assumes every key is a live player. What is the correct fix?
- A. Subscribe to GetPlayspace().PlayerRemovedEvent() and, in the handler, rebuild BankedCounts without the leaver's key.
- B. Wrap the scoring loop in a spawn{} block so the crash happens on another task.
- C. Call GetPlayspace().GetPlayers() once at OnBegin and never update the map again.
- D. Increase the map's size at OnBegin so stale entries are overwritten.
- Which line correctly subscribes a handler to the player-leave lifecycle event?
- A. GetPlayspace().PlayerRemovedEvent().Subscribe(OnDiverLeft)
- B. GetPlayspace().PlayerRemovedEvent.Subscribe(OnDiverLeft)
- C. PlayerRemovedEvent.Subscribe(GetPlayspace(), OnDiverLeft)
- D. GetPlayspace().Subscribe(PlayerRemovedEvent, OnDiverLeft)
- TakeCarried(Diver) returns ?[]int โ the West Coves return-an-option pattern. How do you safely use its result in the cleanup handler?
- A. if (LostRelics := TakeCarried(Leaver)?): โ failable binding with the ? unwrap; the block is skipped when the option is false.
- B. LostRelics := TakeCarried(Leaver).Value โ options expose a .Value field.
- C. Cast it: LostRelics := TakeCarried(Leaver) as []int.
- D. Call it twice โ once to check and once to read.
- Why does OnDiverLeft rebuild CarriedRelics and BankedCounts instead of deleting the leaver's keys directly?
- A. Verse maps have no delete operation โ removal is done by rebuilding the map while filtering out the key (for (Key -> Value : Map, Key <> Leaver)).
- B. Deleting keys is possible but slower than rebuilding.
- C. Rebuilding is required because map keys are immutable strings.
- D. It avoids a garbage-collection pause during the round.
- Your round mixes human divers and AI guardians, and you need cleanup when EITHER kind despawns. Which subscription is correct?
- A. GetPlayspace().ParticipantRemovedEvent().Subscribe(OnParticipantLeft) โ its payload is an agent, covering both humans and bots.
- B. GetPlayspace().PlayerRemovedEvent().Subscribe(OnParticipantLeft) โ it fires for bots too.
- C. Subscribe to both PlayerAddedEvent and PlayerRemovedEvent โ bots trigger the added event in reverse.
- D. GetPlayspace().GetPlayers().Subscribe(OnParticipantLeft) โ GetPlayers streams roster changes.
Answer key
- A โ The lifecycle event is the only reliable signal that a player left. Subscribing to PlayerRemovedEvent and removing the leaver's map entry (rebuild-without-key, since Verse maps have no delete) keeps every [player]-keyed map mirroring the real roster.
- A โ fort_playspace lifecycle events are functions that RETURN a listenable โ so you call PlayerRemovedEvent() with parentheses first, then .Subscribe on the result. Device events like TriggeredEvent are fields and skip the parentheses; mixing up the two is the classic first compile error of this lesson.
- A โ The ? operator unwraps an option inside a failure context. If the leaver never had a map entry, TakeCarried returns false, the binding fails, and the cleanup block skips quietly โ no crash, no special-casing.
- A โ Verse's map type offers no key-removal API. The idiom is to iterate the old map with a filter that excludes the departing key, build a fresh map, and set the variable to it โ the same loop every [player]-keyed system in the capstone uses.
- A โ PlayerRemovedEvent only covers human players. For mixed rounds the lesson's Pattern 1 uses ParticipantRemovedEvent(), whose listenable carries an agent โ then you narrow to player before touching [player]-keyed state. GetPlayers() returns a plain []player snapshot; it is not an event.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ Mid-round disconnect handling so a leaver's relics return to the ledger
Sources
- /guides/detect-player-leave
Lesson 6: Divers vs Wardens: team_settings_and_inventory_device
Objectives
- Student can configure per-team loadouts, spawn rules and inventory grants from Verse for a 2-team asymmetric mode.
Student can configure per-team loadouts, spawn rules and inventory grants from Verse for a 2-team asymmetric mode.
๐ Builds on: center-village using-teams-and-classes lesson
๐งฉ Your capstone piece: The two capstone factions: Divers (harpoon + air canisters) vs Wardens
Quiz
- What is the correct way to use IsOnTeam in Verse?
- A. if (Device.IsOnTeam[Agent]):
- B. if (Device.IsOnTeam(Agent) = true):
- C. Device.IsOnTeam(Agent).Print()
- D. set X = Device.IsOnTeam(Agent)
- TeamOutOfRespawnsEvent is listenable(tuple()). What does its handler signature look like?
- A. OnWiped(Team : team):void
- B. OnWiped(Agent : agent):void
- C. OnWiped():void
- D. OnWiped(Players : []agent):void
- To make Team 2 win when Team 1 is wiped out, which call do you make?
- A. Team1Settings.EndRound()
- B. Team2Settings.EndRound()
- C. Team1Settings.RemoveTemporaryMembersFromTeam()
- D. Team2Settings.GetTeamMembers()
- TeamMemberEliminatedEvent hands you an agent. Why can't you pass it straight to RespawnAtPlayerSpawner?
- A. RespawnAtPlayerSpawner needs a player, so you must narrow the agent first
- B. The agent is already despawned and unusable
- C. RespawnAtPlayerSpawner only accepts a []agent
- D. You must convert it with StringToPlayer
- Which method returns the team's current roster as []agent?
- A. GetTeam()
- B. GetTeamMembers()
- C. IsOnTeam()
- D. GetValue()
Answer key
- A โ IsOnTeam is a <decides> method, so you call it with square brackets inside an if/conditional context โ it succeeds or fails rather than returning a bool.
- C โ A listenable(tuple()) carries no payload, so the handler takes no parameters.
- B โ EndRound() makes the device's OWN team the winner, so you call it on Team 2's device.
- A โ RespawnAtPlayerSpawner(Player:player) requires a player; narrow with if (P := player[Agent]): before calling.
- B โ GetTeamMembers()<reads>:[]agent returns the agents currently on the device's configured team.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The two capstone factions: Divers (harpoon + air canisters) vs Wardens
Sources
- /guides/team-settings-and-inventory-device
Lesson 7: Through the Diver's Mask: First-Person Camera
Objectives
- Student can push/pop the first-person gameplay camera per-agent from Verse (AddTo/RemoveFrom) to switch view on dive enter/exit.
Student can push/pop the first-person gameplay camera per-agent from Verse (AddTo/RemoveFrom) to switch view on dive enter/exit.
๐งฉ Your capstone piece: The underwater first-person dive view, toggled at the water line
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 Athenaeum Depths (feat. the Sunken Dive Round) โ The underwater first-person dive view, toggled at the water line
Sources
- /guides/gameplay-camera-first-person-device
Lesson 8: PROJECT: Sunken Dive Round (integrated mode)
Objectives
- Student assembles trigger plates + team loadouts + first-person cam + relic arrays + leave-cleanup + an air-supply race timeout into one playable dive-round mode.
Student assembles trigger plates + team loadouts + first-person cam + relic arrays + leave-cleanup + an air-supply race timeout into one playable dive-round mode.
๐ Builds on: west-coves async module (race/sync timeout), south-shores enum-with-data phase machine + SpawnProp, center-village custom-round-logic module, east-volcano persistable pattern for [player]int RelicsBanked
๐งฉ Your capstone piece: CAPSTONE PHASE 1: the entire playable device-driven dive round
Quiz
- Playable checkpoint: the round must end when EITHER a diver banks 3 relics OR the air supply expires โ whichever comes first โ and the losing branch must be cancelled immediately. Which structure does the round spine use?
- A. race{} with a Sleep(AirSeconds) branch and an AwaitBankedOut() branch
- B. sync{} so both the timer and the bank-watcher run to completion
- C. spawn{} both tasks and poll a shared logic flag in a loop
- D. Two nested if blocks checked every frame in OnSimulate
- The grader watches CurrentPhase during a full successful round. Which transition sequence is the only legal one?
- A. Lobby โ Diving โ Surfacing โ Debrief
- B. Lobby โ Surfacing โ Diving โ Debrief
- C. Diving โ Lobby โ Debrief โ Surfacing
- D. Lobby โ Diving โ Debrief (Surfacing is optional)
- Why does the device spawn{} one WatchRelicPlate task per plate instead of Subscribe-ing every plate's TriggeredEvent to a single handler?
- A. A Subscribe handler only receives the ?agent, so it cannot know WHICH plate fired; a per-plate awaiting task keeps its own Plate binding and can Disable exactly that plate
- B. Subscribe does not work on trigger_device.TriggeredEvent
- C. spawn{} tasks run faster than subscriptions
- D. Await() delivers the agent while Subscribe delivers only a tuple()
- A diver rage-quits while carrying two relics and having banked one. What does OnPlayerLeft do with the tallies, and why does it matter?
- A. It rebuilds RelicsCarried and RelicsBanked without the leaver, so a ghost entry can never skew BestBanked or be crowned champion
- B. It sets both of the leaver's tallies to 0 but keeps the keys, which is equivalent
- C. Nothing โ Verse removes players from [player]int maps automatically
- D. It transfers the leaver's relics to the tenders' team score
- Why is RelicLedger declared as a module-scoped weak_map(player, int) instead of a [player]int field on the device?
- A. Module-scoped weak_map(player, int) vars persist across game sessions โ the east-volcano persistable pattern โ while device fields reset every round
- B. weak_map is faster to read than [player]int
- C. Device fields cannot store int values keyed by player
- D. Module scope is required for any variable used inside race{}
Answer key
- A โ race{} runs its branches concurrently and cancels the losers the instant one finishes โ exactly the air-timeout-vs-win-condition shape. sync{} waits for ALL branches (the round would never end early), and polling a flag is the pattern the deeps events-over-polling lesson exists to kill.
- A โ RunRound is the single spine: EnterPhase(Lobby), then Diving for the race{}, then Surfacing (cameras popped, ledger banked, relics disposed), then Debrief to crown the champion. Every phase change goes through EnterPhase โ the south-shores one-doorway rule โ so no other order can occur.
- A โ TriggeredEvent is listenable(?agent): the payload identifies the agent, never the device. By awaiting each plate inside its own spawned task, the closure holds the Plate reference, so the watcher can Disable() the exact plate that was claimed โ one relic per plate.
- A โ The deeps leave-cleanup lesson: [player]int entries do NOT vanish when a player leaves. RemoveKey rebuilds each map without the leaver. Keeping a zeroed key is close but still leaves a ghost player that loops, counts, and champion picks must tiptoe around โ sweeping the key is the clean habit.
- A โ Persistence in UEFN comes from module-scoped weak_map(player, T) variables with a persistable value type. The device's [player]int fields (RelicsCarried, RelicsBanked) are per-round working state; RelicLedger is the across-sessions ledger the Phase-2 Athenaeum door will read.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ CAPSTONE PHASE 1: the entire playable device-driven dive round
Sources
- /guides/sunken-dive-round
Lesson 9: Anatomy of the Deep: Entities & Components Mastery
Objectives
- Student can create entities, attach/get components failably, and reason about the simulation entity tree beyond the intro level.
Student can create entities, attach/get components failably, and reason about the simulation entity tree beyond the intro level.
๐ Builds on: south-shores/north-jungle Scene Graph intro lessons (getting-started-in-scene-graph)
๐งฉ Your capstone piece: Foundation for every Athenaeum entity: relics, pedestals, guardian
Quiz
- Why is GetComponent written with square brackets (GetComponent[mesh_component]) and placed inside an if:?
- A. Because square brackets are required syntax for every method call in Verse
- B. Because it returns an array of components that must be looped over
- C. Because getting a component can fail โ the entity might not have one of that kind โ so it must be guarded
- D. Because it modifies the entity, and modifications always need square brackets
- You want a custom component to run an ongoing loop that waits and repeats forever. Which lifetime method should hold that logic?
- A. OnSimulate, because it is <suspends> and is where loops and waits live
- B. OnBeginSimulation, because it runs first at startup
- C. OnAddedToScene, because the component is freshly in the scene
- D. OnEndSimulation, because the loop should keep going until shutdown
- When you build a new component in code, why must you write Entity := Entity (telling it which entity it belongs to)?
- A. It is optional naming; Verse picks the nearest entity if you omit it
- B. It tells the component to copy itself onto every child entity
- C. It links the component to the simulation entity at the root
- D. A component is born attached to exactly one entity for life โ components cannot be moved between parents
- To make an entity's shape vanish temporarily without deleting it, the example calls Mesh.Disable(). What's the advantage of disabling over deleting?
- A. Disabling frees more memory than deleting the entity
- B. The mesh can be turned back on later with Enable(), so the thing can reappear
- C. Deleting a mesh_component is not allowed in Verse
- D. Disabling automatically removes the entity from the tree
- Fill in the blank: to define a component you can bolt onto an entity, you write my_component := class<____>(component):. What goes in the blank?
- A. final_super
- B. suspends
- C. override
- D. editable
Answer key
- C โ Square brackets mark a verb that might fail (the <decides> effect); an entity may lack that component type. The if: safely skips when there's no match. Brackets are not a universal call syntax โ round brackets mark calls that can't fail.
- A โ OnSimulate is the <suspends> main-logic method built for loops, waits, and ongoing behavior. OnBeginSimulation is for instant must-finish-now setup, not for suspending loops.
- D โ The lesson notes components cannot be moved between parents, so each is born attached to one entity for life โ hence you must specify its entity at construction. Verse does not auto-pick or copy it across children.
- B โ Disable()/Enable() flip a component off and on without destroying it, so the object can reappear later โ the basis of the blink-on/off loop. Deleting would be permanent and removing it from the tree is unrelated.
- A โ <final_super> is the required tag every time you make a component intended for an entity โ it promises the class sits directly on top of component. <suspends> and <override> are effects/specifiers for methods, and @editable is an attribute for vars, not a class tag.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ Foundation for every Athenaeum entity: relics, pedestals, guardian
Sources
- /guides/verse-scene-graph-entities-components
Lesson 10: Forge a Custom Component: relic_component
Objectives
- Student can author a custom Verse component (fields, OnBegin/OnSimulate, per-entity data) and add it to entities at edit- and run-time.
Student can author a custom Verse component (fields, OnBegin/OnSimulate, per-entity data) and add it to entities at edit- and run-time.
๐ Builds on: north-jungle classes/interfaces lessons (OO layering)
๐งฉ Your capstone piece: relic_component: carries relic ID, value, banked-state on every treasure entity
Quiz
- Why must a call like `Entity.GetComponent[light_component]` be wrapped in an `if` (or used with `or`)?
- A. Because GetComponent is a failable lookup that may not find the component
- B. Because light_component is a device
- C. Because GetComponent runs asynchronously
- D. Because Verse forbids square brackets outside loops
- What does a `light_component` depend on to know where it shines?
- A. A trigger_device on the same entity
- B. A transform_component on the entity
- C. The creative_device's OnBegin
- D. A material asset
- How do you reference a placed Scene Graph entity so Verse can act on it?
- A. Call entity{} directly in OnBegin
- B. Declare an @editable entity field on a creative_device and assign it in the Details panel
- C. Use StringToMessage to name it
- D. It happens automatically for every entity in the level
- Which method returns the direct child entities of an entity?
- A. GetComponents()
- B. GetLocalTransform()
- C. GetEntities()
- D. Enable()
- Why do the transform examples write `20.0` and `100.0` instead of `20` and `100`?
- A. Style preference only
- B. Verse does not auto-convert int to float, and translation vectors are float
- C. Integers are not allowed in Scene Graph at all
- D. Because SetLocalTransform rejects positive numbers
Answer key
- A โ The `[]` form is a failable query โ the entity might not have that component โ so it must appear in a failure context like an `if`.
- B โ The API notes that a transform_component on the entity positions the light.
- B โ Like any placed thing, an entity must be held in an @editable field on a creative_device to be referenced from Verse.
- C โ GetEntities() returns the []entity direct children; GetComponents() returns the []component on the entity itself.
- B โ Vector translations are float components; Verse won't implicitly convert an int, so float literals are required.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ relic_component: carries relic ID, value, banked-state on every treasure entity
Sources
- /guides/scene-graph-add-component
Lesson 11: Currents and Coordinates: Hierarchy & Transforms
Objectives
- Student can parent/unparent entities and manipulate local vs global transforms (translate/rotate/scale) through the hierarchy.
Student can parent/unparent entities and manipulate local vs global transforms (translate/rotate/scale) through the hierarchy.
๐งฉ Your capstone piece: Pedestal sockets: banked relics parent to their pedestal and inherit its transform
Quiz
- You make the bulb a CHILD of the pole, then move the pole 10 meters left. What happens to the bulb?
- A. It stays behind, floating where it was
- B. It moves 10 meters left only if it shares the pole's transform_component
- C. It rides along, staying perfectly on top of the pole
- D. It moves but rotates to face the pole's old position
- Why does GetEntities() use round brackets while GetParent[] uses square brackets?
- A. GetEntities() can't fail (it returns a possibly-empty list); GetParent[] can fail (the root has no parent)
- B. Round brackets are for methods that change the tree; square for methods that read it
- C. GetEntities() returns one entity; GetParent[] returns many
- D. It's a stylistic choice with no functional difference
- The rising_platform_component reads GetLocalTransform() and rises relative to that. Why use the LOCAL transform instead of the global one?
- A. Because the global transform can't be changed at runtime
- B. Because local transforms are faster to compute than global ones
- C. Because only the local transform includes the Up axis
- D. So the platform always rises relative to its parent, no matter where the parent sits in the world
- You call SetLocalTransform on an entity that has no transform_component yet. What happens?
- A. It throws an error because the position-power is missing
- B. Verse creates a transform_component for it and sets the local transform
- C. It silently does nothing until you add a transform_component manually
- D. It sets the global transform instead as a fallback
- You want BulbEntity to become a child of PoleEntity. Which call is correct?
- A. PoleEntity.AddEntities(array{BulbEntity})
- B. BulbEntity.AddEntities(array{PoleEntity})
- C. BulbEntity.SetParent(PoleEntity)
- D. PoleEntity.GetEntities(BulbEntity)
Answer key
- C โ The golden rule of hierarchy: when the parent moves, every child moves with it. That's the whole reason to parent the bulb to the pole instead of leaving them unrelated.
- A โ The bracket shape signals risk: GetEntities() always succeeds, handing back a (maybe empty) list, while GetParent[] can fail because the root entity has no parent โ so it's guarded.
- D โ Using the local transform makes the rise relative to home, so it works wherever the parent is placed. The apartment-address analogy: local 'third door on the left' is unchanged when the whole building moves.
- B โ Per the docs cited, SetLocalTransform creates a transform_component if one doesn't exist and sets it โ Verse fills in the position-power for you, rather than erroring or no-op'ing.
- A โ AddEntities is called ON the parent and says 'adopt these entities as my children' โ so the pole adopts the bulb. It takes an array, hence the array{...} wrapper even for a single child.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ Pedestal sockets: banked relics parent to their pedestal and inherit its transform
Sources
- /guides/verse-scene-graph-hierarchy-transforms
Lesson 12: The Swimming Relic: Animate-to-Targets Component
Objectives
- Student can build a reusable component that eases an entity through keyframed target transforms over time (the scene-graph animation pattern above MoveTo/prop_mover).
Student can build a reusable component that eases an entity through keyframed target transforms over time (the scene-graph animation pattern above MoveTo/prop_mover).
๐ Builds on: animation arc: south/north MoveTo/TeleportTo + prop_mover lessons; west-coves async (Sleep/loop timing)
๐งฉ Your capstone piece: Banked relics SWIM along a spline of targets to their Athenaeum pedestal
Quiz
- In this lesson's Scene Graph model, what is the relationship between an Entity and a Component?
- A. Entities and Components are the same thing with different names
- B. The Component is the object and the Entity is a script inside it
- C. An Entity can only ever have one Component
- D. The Entity is the object (like the crate) and the Component is a behavior or capability attached to it
- Which built-in component does slide_crates_component look up on its entity to handle the actual movement?
- A. spline_path_component
- B. transform_animator_component
- C. keyframed_movement_component
- D. prop_mover_component
- With `Easing := ease_out_cubic_bezier_easing_function{}`, how does the crate's slide look?
- A. It starts slow and speeds up toward the target
- B. It teleports instantly to the target position
- C. It moves at a perfectly constant speed the whole way
- D. It starts fast and slows down as it reaches the target, like a car pulling into a parking spot
- You copy the walkthrough code but delete the line `Mover.Play()`. What happens when you hit Play in UEFN?
- A. The crate moves anyway, because SetKeyframes starts the animation automatically
- B. The crate moves at double speed
- C. The crate never moves โ the keyframes are loaded but the animation is never started
- D. The game crashes on startup
- Fill in the blank: `Mover.____(array{Slide}, oneshot_keyframed_movement_playback_mode{})` is the call that loads our keyframe playlist into the motor.
- A. AddKeyframe
- B. SetKeyframes
- C. RegisterMovement
- D. LoadPath
Answer key
- D โ The article compares it to a family tree: the Entity is the 'person' (e.g., The Crate) and the Component is what they do or have (e.g., Movement). Components are attached to Entities like giving a character a jetpack item.
- C โ The walkthrough fetches the 'motor' with `Entity.GetComponent[keyframed_movement_component]`, loads it with `SetKeyframes`, then calls `Play()`.
- D โ Ease-out means starts fast, slows down at the end โ the article's example is a car pulling into a parking spot or a door closing. Ease-in is the opposite (good for launches), and linear is the constant 'robot walk'.
- C โ SetKeyframes only loads the 'playlist'; `Mover.Play()` is what actually starts the animation. Without it, the crate just sits at its start position (and FinishedEvent never fires).
- B โ The walkthrough uses `Mover.SetKeyframes(array{Slide}, oneshot_keyframed_movement_playback_mode{})` โ an array of keyframed_movement_delta plus a playback mode (oneshot = play once and stop).
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ Banked relics SWIM along a spline of targets to their Athenaeum pedestal
Sources
- /guides/animate-to-targets-component-verse
Lesson 13: Blueprint of the Vault: Build a Relic PREFAB
Objectives
- Student can package mesh + components + child entities into a reusable prefab, spawn instances at runtime, and version it for import by other islands/zones.
Student can package mesh + components + child entities into a reusable prefab, spawn instances at runtime, and version it for import by other islands/zones.
๐ Builds on: lesson 10 relic_component + lesson 12 animate-to-targets component
๐งฉ Your capstone piece: The relic prefab: one template, N spawned treasures per round
Quiz
- Your vault must spawn 5 relic prefab instances at pedestal entities tagged relic_spawn_point, scattered several levels deep under the vault entity. Which call finds all of those pedestals from the vault's component?
- A. Entity.FindDescendantEntitiesWithTag(relic_spawn_point)
- B. Entity.GetEntities()
- C. Entity.FindDescendantComponents(relic_spawn_point)
- D. GetSimulationEntity[].GetComponents()
- You spawn 5 relics from one prefab template, then later edit the prefab asset โ swapping its mesh. What is the key semantic difference between prefab instances and deep copies here?
- A. Prefab instances keep a live link to the template and pick up the new mesh; a deep copy is a one-time snapshot with no link, so it would keep the old mesh.
- B. There is no difference โ both always update when the source changes.
- C. Deep copies update automatically; prefab instances must each be re-spawned by hand.
- D. Prefab instances share one entity in memory, so editing any instance edits all of them at runtime.
- In the factory, MakeRelic builds a fresh entity{} and the spawner calls SpawnPoint.AddEntities(array{Relic}). Why does the relic appear exactly at the pedestal?
- A. A fresh entity's local transform is identity, and local transforms are relative to the parent โ so parenting it under the pedestal places it right on the pedestal.
- B. AddEntities teleports entities to the world origin, and the pedestal is at the origin.
- C. entity{} copies the transform of the component that constructed it.
- D. You must always call SetGlobalTransform after AddEntities or the relic spawns at (0,0,0) in world space.
- Epic's Scene Graph guidance says to keep gameplay logic in components rather than adding code to your entity (prefab) subclass. Why?
- A. Prefabs get restructured throughout production โ component-held logic lets you rearrange the prefab freely without refactoring a class hierarchy, and the components stay reusable elsewhere.
- B. Entity subclasses cannot contain any Verse code at all.
- C. Components run faster than entity methods at runtime.
- D. Because only one entity subclass is allowed per island.
- The vault has RelicsPerRound set to 5, but a designer only tagged 3 pedestal entities with relic_spawn_point. When the session launches, how many relics spawn?
- A. 3 โ the for loop iterates once per tagged pedestal found, and the RelicsPerRound cap only limits the count downward, it cannot invent extra spawn points.
- B. 5 โ the loop keeps spawning until SpawnedCount reaches RelicsPerRound, stacking extras on the last pedestal.
- C. 0 โ the count mismatch makes FindDescendantEntitiesWithTag fail.
- D. 5 โ the two leftover relics spawn at the world origin.
Answer key
- A โ `FindDescendantEntitiesWithTag` walks the whole hierarchy beneath the entity and yields every descendant entity carrying the tag type. `GetEntities()` only returns direct children (one level down), and `FindDescendantComponents` searches for components, not tagged entities.
- A โ This is THE prefab payoff: instances reference the template, so shipping version 2 of the relic means editing one asset and every placed or spawned instance follows on the next build. A deep-copied entity tree duplicates everything with no back-reference โ it is frozen at copy time.
- A โ Local transforms are parent-relative (lesson 11). A new entity{} has an identity local transform, so the instant AddEntities parents it under the pedestal, 'no offset from my parent' means 'sitting exactly on the pedestal'. Moving the pedestal later moves the relic with it.
- A โ Straight from the entity docs: deriving from entity defines a prefab, but behaviour belongs in components so the prefab's structure and content can change at any time without a massive class refactor. Bonus: the same relic_component drops into any other prefab that needs an identity card.
- A โ OnSimulate loops over the pedestals that FindDescendantEntitiesWithTag actually returns โ three, here โ and spawns at most one relic per pedestal while SpawnedCount < RelicsPerRound. With only 3 tagged pedestals the loop body runs 3 times, so 3 relics spawn. The cap can shrink the spawn count, never grow it.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The relic prefab: one template, N spawned treasures per round
Sources
- /guides/scene-graph-prefabs
Lesson 14: UMG Widgets with Verse Custom Data Fields
Objectives
- Student can build a UMG widget blueprint with custom data fields and drive them live from Verse (data binding), the newest UI workflow.
Student can build a UMG widget blueprint with custom data fields and drive them live from Verse (data binding), the newest UI workflow.
๐ Builds on: north-jungle UI widgets; east-volcano data layers
๐งฉ Your capstone piece: The dive-round scoreboard + oxygen meter UMG overlay driven by Verse data fields
Quiz
- At runtime, which side owns the value shown in a custom data field โ the widget blueprint or the Verse device that sets it?
- A. The Verse device. The widget's binding is a read-only view; every change flows from Verse calls like SetValue, and the widget repaints to match.
- B. The widget blueprint. Verse can only read the value the widget computes in its bindings.
- C. Both share ownership โ whichever wrote most recently wins, so you must lock the field before writing.
- D. Neither โ the HUD Controller device owns all bound values and both sides read from it.
- Your dive round needs each diver's scoreboard to show their OWN banked-relic total. Which tracker_device call updates the data field for just one player?
- A. ScoreTracker.SetValue(BankingAgent, NewScore) โ the per-agent overload
- B. ScoreTracker.SetValue(NewScore) โ SetValue is always per-player automatically
- C. ScoreTracker.SetTarget(NewScore) โ Target holds the per-player value
- D. ScoreTracker.SetTitleText(NewScore) โ the title field carries the number
- What role does the Device โ Tracker View Model play in the UMG workflow?
- A. It is the bridge that exposes the tracker device's data fields (Value, Target, Title Text, Description Text) so widget View Bindings can render them.
- B. It compiles the widget blueprint into Verse code so the device can call widget functions directly.
- C. It replaces the tracker device โ once the viewmodel exists, no device is needed on the island.
- D. It sends player input from the widget back into Verse event handlers.
- Your oxygen progress bar must drain from full to empty over the round. How does the bar get its percent?
- A. Bind Percent to a conversion that divides the viewmodel's Value by its Target โ so Verse must SetTarget before the drain starts.
- B. Call SetPercent on the tracker device every second from Verse.
- C. The progress bar reads OxygenRemaining directly from the Verse device's @editable field.
- D. Animate the bar in the widget blueprint with a 90-second timeline that starts on construct.
- In the drain loop, OxygenRemaining has just ticked down to 10. Besides the usual SetValue write, what does the walkthrough device do on that iteration?
- A. It calls OxygenTracker.SetDescriptionText(LowAirWarning), so a widget element bound to Description Text can show "Surface soon, diver!"
- B. It breaks out of the loop and prints the round-over message early.
- C. It calls SetTarget(10) so the progress bar recalibrates for the final seconds.
- D. It unassigns the tracker so the oxygen bar disappears from screen.
Answer key
- A โ Data binding is one-directional here: the Verse device (through the tracker's data fields) is the single source of truth, and the widget binding simply renders whatever the field holds. If the display is wrong, you debug the Verse side โ the owner of the value.
- A โ SetValue has three overloads: SetValue(Value) for all active players, SetValue(TeamIndex, Value) for a team, and SetValue(Agent, Value) for one agent. The per-agent overload is what makes the same widget blueprint show different data per player.
- A โ The viewmodel is the binding layer. Added via Window โ Viewmodels in the widget editor, it surfaces the tracker's data fields so the widget can bind text blocks, progress bars, and other elements to them. The device still owns and writes the data from Verse.
- A โ The widget binding computes Value รท Target. Verse sets Target once (the full oxygen supply) and then writes Value each second; the binding recomputes the percent on every change. That's why the walkthrough calls SetTarget and SetValue before the drain loop begins.
- A โ The loop checks if (OxygenRemaining = 10) and calls SetDescriptionText(LowAirWarning). Even flavour text is a data field โ a widget label bound to Description Text repaints when it changes. The break only happens when OxygenRemaining reaches 0.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The dive-round scoreboard + oxygen meter UMG overlay driven by Verse data fields
Sources
- /guides/umg-custom-data-fields
Lesson 15: Dress the Deep: Custom Asset Components (Mesh, VFX, SFX)
Objectives
- Student can attach and drive mesh_component, particle/VFX and sound components from Verse so an entity looks, glows and sounds alive.
Student can attach and drive mesh_component, particle/VFX and sound components from Verse so an entity looks, glows and sounds alive.
๐ Builds on: lesson 13 relic prefab; east-volcano vfx-spawner-device + verse-sound-triggered-sfx patterns
๐งฉ Your capstone piece: Relic presentation: glowing mesh, bubble-trail VFX, sonar-ping SFX on proximity
Quiz
- Your relic's glow mesh must vanish and a one-shot SFX must play the instant relic_component flips to carried. Which references are set in the EDITOR, and which does VERSE drive at runtime?
- A. Editor sets the mesh asset and sound wave on their components; Verse gets handles via GetComponent and calls Disable() / Play() at runtime.
- B. Verse sets the mesh asset path and sound wave path as strings; the editor only toggles visibility.
- C. Everything is editor-set โ Verse cannot touch mesh or sound components at runtime.
- D. Everything is Verse-set โ asset components ignore their Details panel once the game starts.
- relic_component exposes CarriedChangedEvent : event(logic). How does relic_presentation_component react to it?
- A. Run a loop that calls CarriedChangedEvent.Await() โ a custom event(t) supports Signal and Await, but not Subscribe.
- B. Call CarriedChangedEvent.Subscribe(OnCarriedChanged), exactly like a device event.
- C. Poll Relic.Carried every frame inside a TickEvent callback.
- D. Custom events can only be heard by the component that declared them.
- Why is Entity.GetComponent[mesh_component] written with square brackets and wrapped in an if?
- A. GetComponent has the <decides> effect โ it fails if the entity has no such component, so the call must be guarded in a failure context.
- B. Square brackets mean the result is an array of every mesh on the entity.
- C. Square brackets make the lookup faster by skipping type checks.
- D. It is only a style convention; round parentheses work identically.
- The sonar heartbeat is an endless Sleep-then-ping loop. Which component lifecycle slot should hold it, and why?
- A. OnSimulate<override>()<suspends> โ it is the slot designed for long-running suspending work, and it is cancelled automatically when the entity leaves the scene.
- B. OnBeginSimulation โ long loops belong in setup so they start as early as possible.
- C. OnAddedToScene โ loops must start before simulation begins.
- D. OnEndSimulation โ the loop should run while the component shuts down.
- OnSimulate runs `sync: WatchCarried() SonarLoop()`. What does sync accomplish here?
- A. It runs WatchCarried and SonarLoop concurrently in the one <suspends> lifecycle slot, both cancelled together when the entity leaves the scene.
- B. It runs WatchCarried, waits for it to finish, then runs SonarLoop.
- C. It forces the two loops to execute on the same simulation tick.
- D. It creates a lock so only one of the two loops can access the relic_component at a time.
Answer key
- A โ Asset components split the job: the editor's Details panel decides WHAT asset the component renders or plays (mesh, Niagara system, sound wave), while Verse decides WHEN โ grabbing a handle with Entity.GetComponent[...] and calling Enable/Disable/Play/Stop. Verse never assigns the asset itself.
- A โ Verse's event(t) class implements signalable and awaitable โ Signal(Val) and Await() โ but NOT subscribable. Subscribe belongs to listenable events like a trigger_device's TriggeredEvent. So the listener runs an Await loop (in a <suspends> context such as OnSimulate), which is also the lesson-3 'events over polling' pattern.
- A โ GetComponent is declared <reads><decides> โ a failable call. Square brackets mark failable invocation in Verse, and failable calls must run inside a failure context like if. If the entity lacks that component, the if simply skips โ which is why a half-dressed prefab degrades gracefully instead of crashing.
- A โ OnSimulate carries the <suspends> effect and is guaranteed to start after OnBeginSimulation; the Scene Graph cancels its task before OnEndSimulation, so loops written there clean themselves up when the entity is removed. OnBeginSimulation is plain ():void โ immediate setup only (you'd have to spawn a loop from there).
- A โ sync launches its child <suspends> jobs concurrently in the same task; the Scene Graph's cancellation of OnSimulate when the entity leaves the scene tears down both loops cleanly. It is not sequential and does not add locking.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ Relic presentation: glowing mesh, bubble-trail VFX, sonar-ping SFX on proximity
Sources
- /guides/deeps-custom-asset-component
Lesson 16: Skeletal Mesh Components & Animation Playback
Objectives
- Student can add a skeletal mesh component to a scene-graph entity and play/blend skeletal animations from Verse โ the step between ease/prop animation and control rig.
Student can add a skeletal mesh component to a scene-graph entity and play/blend skeletal animations from Verse โ the step between ease/prop animation and control rig.
๐ Builds on: east-volcano prop_mover + ease animations
๐งฉ Your capstone piece: The Athenaeum guardian's swim/idle skeletal animations before control-rig takes over
Quiz
- Order the island's animation arc from first rung to last: which sequence is correct?
- A. MoveTo โ ease curves โ prop_mover โ skeletal playback โ Control Rig
- B. Control Rig โ skeletal playback โ prop_mover โ ease curves โ MoveTo
- C. prop_mover โ MoveTo โ Control Rig โ ease curves โ skeletal playback
- D. ease curves โ MoveTo โ skeletal playback โ prop_mover โ Control Rig
- What is the complete public Verse transport surface of the Animated Mesh device?
- A. Play(), Pause(), and PlayReverse()
- B. Play(), Stop(), SetClip(), and Blend()
- C. PlayAnimation(Sequence) with any imported animation asset
- D. Enable(), Disable(), and SetPlayRate()
- The guardian needs both a swim clip and an idle clip. Why does the lesson use TWO Animated Mesh devices?
- A. One device holds one mesh and ONE clip chosen in the editor, so each clip needs its own device โ swapping which device is 'on stage' is the clip switch.
- B. A single device cannot loop an animation, so the second device restarts the first.
- C. Two devices are required so one can play while the other rewinds the shared clip.
- D. Animated Mesh devices can only play 30 seconds of animation each.
- Your guardian entity carries a skeletal mesh added in the editor. What can Verse legally do with its mesh_component?
- A. Find it with Entity.GetComponent[mesh_component] and manage it โ Enable/Disable, Visible, collision, overlap events โ but never construct one or tell it to play a clip.
- B. Construct a new mesh_component{} in code and attach it with AddComponents.
- C. Call PlayAnimation on it with any animation asset from the Content Browser.
- D. Nothing โ mesh_component is completely invisible to Verse.
- Why is PlaySkeletalAnimation shown in the lesson but explicitly marked 'do not ship this'?
- A. It is @experimental โ projects using it cannot be published, and skeletal_animation values only exist as imported project assets.
- B. It only works on static meshes, not skeletal meshes.
- C. It requires a paid UEFN license tier.
- D. It has been fully deprecated and removed from the Verse.digest.verse.
Answer key
- A โ The arc climbs from moving whole props in code (MoveTo), to making that motion feel alive (ease), to editor-configured movement (prop_mover), to playing baked bone animation (skeletal playback, this lesson), and finally to authoring bone motion yourself (Control Rig).
- A โ animated_mesh_device exposes exactly three calls: Play(), Pause(), and PlayReverse(). The skeletal mesh and its one animation clip are chosen on the device in the editor โ Verse only drives the transport.
- A โ The device's animation is fixed in the editor and Verse has no SetClip call. With one device per clip โ both playing continuously โ teleporting a puppet on or off stage swaps clips instantly with no restart hitch.
- A โ mesh_component is <epic_internal>: the editor attaches it and Verse cannot construct or subclass it. Verse CAN retrieve it (GetComponent is failable) and manage rendering, collision, queryability, and overlap events. Clip playback goes through the Animated Mesh device โ the experimental PlaySkeletalAnimation entity API is not publishable yet.
- A โ The article calls out PlaySkeletalAnimation as @experimental: it compiles only with experimental features enabled, and experimental projects cannot be published. It is shown 'for the map, not the trip' โ the publishable path today is the Animated Mesh device.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The Athenaeum guardian's swim/idle skeletal animations before control-rig takes over
Sources
- /guides/scene-graph-skeletal-animation
Lesson 17: Lights of the Athenaeum: Cinematic Sequencer
Objectives
- Student can author a Level Sequence in Sequencer (camera cuts, prop tracks) and play/stop/scrub it per-player from Verse via cinematic_sequence_device.
Student can author a Level Sequence in Sequencer (camera cuts, prop tracks) and play/stop/scrub it per-player from Verse via cinematic_sequence_device.
๐ Builds on: UI arc: center-village HUD lessons (sequencer completes the UI arc and unlocks data layers); cinematic-trigger-chain (compile-passing helper)
๐งฉ Your capstone piece: The vault-opening cutscene when the final relic is banked
Quiz
- The device's *Plays For* setting is **Instigator**. Which call correctly starts the sequence for the player who pressed a button?
- A. Cutscene.Play()
- B. Cutscene.Play(Agent)
- C. Cutscene.Play(?Agent)
- D. Play(Cutscene, Agent)
- What is the correct handler signature for StoppedEvent?
- A. OnStopped(Agent : ?agent):void
- B. OnStopped(Agent : agent):void
- C. OnStopped():void
- D. OnStopped(Frame : int):void
- Why does `Cutscene.SetPlayRate(1)` fail to compile?
- A. SetPlayRate doesn't exist
- B. 1 is an int but SetPlayRate needs a float; Verse won't auto-convert
- C. PlayRate must be between 0 and 1
- D. You must pass an agent
- Which method snaps the sequence to its final frame and halts, leaving the end pose in place?
- A. Stop()
- B. Pause()
- C. GoToEndAndStop()
- D. PlayReverse()
- Why must the cinematic be declared as an `@editable` field in the class?
- A. To make it run faster
- B. So Verse can bind it to the real placed device; a bare device reference fails with Unknown identifier
- C. Because StoppedEvent requires it
- D. It is optional and only for cosmetics
Answer key
- B โ Any *Plays For* setting other than Everyone requires the agent overload: Play(Agent).
- C โ StoppedEvent is listenable(tuple()) โ an empty payload โ so the handler takes no parameters.
- B โ SetPlayRate takes a float. Verse does not convert int to float automatically โ use 1.0.
- C โ GoToEndAndStop moves to the end before stopping; Stop just halts at the current position.
- B โ Only an @editable field bound in the editor references a placed device. Calling methods on a non-editable inline device fails to resolve.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The vault-opening cutscene when the final relic is banked
Sources
- /guides/cinematic-sequence-device
Lesson 18: The Hidden Wing: Data Layers Reveal
Objectives
- Student can organize content into World Partition data layers and activate/deactivate them at runtime (via sequencer/device) to reveal the inner Athenaeum.
Student can organize content into World Partition data layers and activate/deactivate them at runtime (via sequencer/device) to reveal the inner Athenaeum.
๐ Builds on: lesson 15 sequencer (data layers are driven from the sequence per the owner UI arc)
๐งฉ Your capstone piece: The sealed inner library wing that materializes when all pedestals are filled
Quiz
- What device does `secret_door_device` extend to become a placeable Creative Device?
- A. creative_device
- B. trigger_device
- C. prop_manipulator_device
- D. creative_prop
- In the tutorial's UEFN setup steps, what are the TWO Data Layers you create first?
- A. Walls and Treasure
- B. Day and Night
- C. Floor and Ceiling
- D. Spikes and Chest
- Which line makes the code wait until a player steps on the trigger pad?
- A. TriggerPad.TriggeredEvent.Await()
- B. TriggerPad.OnBegin()
- C. TriggerPad.Wait()
- D. on_player_touch()
- Fill in the blank: `OnBegin<override>()____ : void =` โ what effect specifier is required because the body awaits an event?
- A. <suspends>
- B. <transacts>
- C. <varies>
- D. <decides>
- Inside `on_player_touch()`, what does the `for (Wall : WallProps): Wall.Disable()` loop accomplish?
- A. Hides every wall prop in the WallProps array
- B. Deletes the WallProps array
- C. Shows every treasure prop
- D. Waits for the trigger to fire again
Answer key
- A โ `secret_door_device := class(creative_device):` makes the class a Creative Device you can place in your level, just like the article's code shows.
- A โ Step 1 says to click 'Add Data Layer' twice, naming them `Walls` and `Treasure`, matching the two prop arrays used later in the code.
- A โ `TriggerPad.TriggeredEvent.Await()` suspends execution until the trigger's event fires, which is why `OnBegin` needs the `<suspends>` effect specifier.
- A โ Because `OnBegin` calls `.Await()`, which can pause execution, it must be marked `<suspends>` as shown in the article's code block.
- A โ The loop iterates over every `prop_manipulator_device` in `WallProps` and calls `.Disable()` on each, making the walls disappear, per the 'How the Code Works' section.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The sealed inner library wing that materializes when all pedestals are filled
Sources
- /guides/world-partition-data-layers
Lesson 19: Warden of the Depths: Advanced NPC Behavior
Objectives
- Student can author a custom npc_behavior Verse class with a patrol/chase/return state machine, spawn it via npc_spawner_device, and react to perception.
Student can author a custom npc_behavior Verse class with a patrol/chase/return state machine, spawn it via npc_spawner_device, and react to perception.
๐ Builds on: west-coves state-machine-with-npc-behavior + async toolkit; north-jungle classes/interfaces
๐งฉ Your capstone piece: The kraken-guardian NPC that patrols the Athenaeum and chases relic carriers
Quiz
- Where must you attach an `npc_behavior` subclass to make it run?
- A. Place it directly in the level as a creative device
- B. Assign it as the Verse Behavior in an NPC Character Definition asset
- C. Add it as a component to a trigger_device
- D. Reference it from a timer_device's SuccessEvent
- What is the correct way to call `GetAgent` inside an `npc_behavior`, given that it is marked `<decides>`?
- A. Agent := GetAgent()
- B. if (A := GetAgent[]):
- C. Agent := GetAgent?
- D. GetAgent.Subscribe(OnAgent)
- You want the search countdown to freeze while the player hides behind a crate. Which `timer_device` method pair should you use?
- A. Stop() and Start()
- B. Disable() and Enable()
- C. Pause() and Resume()
- D. ResetForAll() and StartForAll()
- What does passing `0` to `trigger_device.SetMaxTriggerCount(0)` do?
- A. Disables the trigger permanently
- B. Allows the trigger to fire an unlimited number of times
- C. Prevents the trigger from ever firing
- D. Resets the trigger count to its default value
- An `npc_behavior`'s `OnBegin` override is marked `<suspends>`. What happens if `OnBegin` returns early (before the NPC despawns)?
- A. The NPC is immediately eliminated
- B. The NPC's custom Verse behavior ends; it may fall back to default or stand idle
- C. The engine automatically restarts OnBegin in a loop
- D. A compile error is raised because OnBegin must never return
Answer key
- B โ `npc_behavior` is not a `creative_device` and cannot be placed in the level. It must be attached to an NPC Character Definition asset (or overridden in an npc_spawner_device) so the engine knows which NPC to run it on.
- B โ In Verse, a `<decides>` function is called with the `[]` suffix inside an `if` expression: `if (A := GetAgent[]):`. This safely unwraps the failable result.
- C โ `Pause()` freezes the timer at its current value and `Resume()` continues from that same value. `Stop`/`Start` would restart the countdown, and `Disable`/`Enable` prevents the device from receiving signals entirely.
- B โ Per the API docs, `0` passed to `SetMaxTriggerCount` indicates no limit on trigger count โ the device can fire indefinitely.
- B โ `OnBegin` is a suspending coroutine that drives the NPC's custom logic. If it returns, the custom behavior simply ends. The NPC won't be eliminated, but it will stop executing your Verse state machine. Use a `loop:` to keep the behavior alive.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The kraken-guardian NPC that patrols the Athenaeum and chases relic carriers
Sources
Lesson 20: Puppet the Kraken: Animation up to CONTROL RIG
Objectives
- Student can rig a skeletal mesh with Control Rig, keyframe rig controls in Sequencer, and trigger the rig-driven animation in-game โ the summit of the island's animation arc.
Student can rig a skeletal mesh with Control Rig, keyframe rig controls in Sequencer, and trigger the rig-driven animation in-game โ the summit of the island's animation arc.
๐ Builds on: whole animation arc: MoveTo/TeleportTo (south) -> ease (north) -> prop_mover (east) -> animate-to-targets (lesson 12) -> sequencer (lesson 15)
๐งฉ Your capstone piece: The kraken guardian's tentacle-sweep animation in the victory cutscene and its idle sway in-world
Quiz
- Match the tool to the scenario: (1) a vault door slides open along one axis with editor settings only, (2) a spawned relic swims along a chain of authored waypoints to its pedestal, (3) a platform must move to a destination your Verse code computes at runtime, (4) the guardian's tentacles flex bone-by-bone in a keyframed flourish. Which mapping is right?
- A. (1) prop_mover, (2) animate-to-targets, (3) MoveTo, (4) Control Rig
- B. (1) MoveTo, (2) prop_mover, (3) Control Rig, (4) animate-to-targets
- C. (1) Control Rig, (2) MoveTo, (3) prop_mover, (4) animate-to-targets
- D. (1) animate-to-targets, (2) Control Rig, (3) prop_mover, (4) MoveTo
- In the guardian conductor device, why is the `SweepPlaying` flag required before restarting the idle sway in the `StoppedEvent` handler?
- A. Because StoppedEvent fires on EVERY stop โ including our own IdleSway.Stop() call โ so without the flag the idle would restart on top of the tentacle sweep.
- B. Because StoppedEvent only fires once per game session, so the flag re-arms it.
- C. Because two cinematic_sequence_devices can never play at the same time.
- D. Because Verse forbids calling Play() from inside an event handler.
- What is the correct handler signature for `cinematic_sequence_device.StoppedEvent`, and why?
- A. OnStopped() : void โ StoppedEvent is a listenable(tuple()), an empty payload, so the handler takes no parameters.
- B. OnStopped(Agent : agent) : void โ every device event sends the instigating agent.
- C. OnStopped(Frame : int) : void โ it sends the frame the sequence stopped on.
- D. OnStopped(Agent : ?agent) : void โ it sends an optional agent like a trigger does.
- In the Control Rig editor, what is the relationship between a control and a bone?
- A. A control is an animator-facing handle; the Rig Graph's forward solve wires the control's transform to drive the bone, so keyframing the control poses the bone.
- B. A control replaces the bone in the skeleton hierarchy permanently.
- C. Controls are only for physics simulation; bones are keyframed directly in Sequencer.
- D. A control is a Verse object you instantiate with control{} at runtime.
- In OnBegin, the device subscribes to StoppedEvent for both IdleSway and TentacleSweep BEFORE calling IdleSway.Play(). What would most likely go wrong if IdleSway.Play() were called before the Subscribe() calls?
- A. If the sequence finished and fired StoppedEvent before the subscription registered, the handler would never run and the idle sway would not loop.
- B. Nothing -- subscription order relative to Play() never matters in Verse.
- C. The device would fail to compile.
- D. TentacleSweep would start playing automatically.
Answer key
- A โ prop_mover is the no-code, editor-configured slider; animate-to-targets moves a scene-graph entity along authored targets; MoveTo is the Verse call for runtime-computed destinations; and only Control Rig can pose individual bones of a skeletal mesh for a keyframed performance. Each tool owns exactly one rung of the arc.
- A โ `StoppedEvent` does not distinguish a natural end from a manual `Stop()`. Calling `IdleSway.Stop()` in the victory handler immediately fires `OnIdleStopped`, and without the guard flag that handler would call `IdleSway.Play()` again mid-sweep.
- A โ The live digest declares `StoppedEvent<public>:listenable(tuple())` โ an empty tuple payload. Your handler must take no parameters, unlike `trigger_device.TriggeredEvent` which is `listenable(?agent)`.
- A โ Controls are the grabbable handles you add in the Rig Hierarchy; the Forward Solve graph (e.g. Get Transform - Control feeding Set Transform - Bone, or a FABRIK chain) makes bones follow them. Sequencer keyframes the controls, and the rig poses the skeleton.
- A โ Subscribing first guarantees the handler is wired before any stop can fire. If Play() ran first and the sequence ended quickly, a StoppedEvent could fire with no listener attached yet, breaking the loop.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ The kraken guardian's tentacle-sweep animation in the victory cutscene and its idle sway in-world
Sources
- /guides/control-rig-guardian-animation
Lesson 21: CAPSTONE: Athenaeum Depths
Objectives
- Student ships the full two-phase minigame: the device-driven Sunken Dive Round fused with the scene-graph relic economy, sequencer/data-layer reveal, control-rig kraken and persistent progress.
Student ships the full two-phase minigame: the device-driven Sunken Dive Round fused with the scene-graph relic economy, sequencer/data-layer reveal, control-rig kraken and persistent progress.
๐ Builds on: EVERYTHING: sunken-dive-round (lesson 8), relic prefab stack (10-14), sequencer+data layers (15-16), NPC+rig (17-18), plus cross-zone modules โ west-coves async toolkit, center-village round-logic + teams, east-volcano persistable score module, south-shores SpawnProp + phase-enum, north-jungle event-bus module
๐งฉ Your capstone piece: THE capstone โ the shippable UEFN island launched from the AweShucks Town matchmaking portal
Quiz
- Final boss checklist: in playtest the relic counter climbs and relics swim to pedestals, but the hidden wing never reveals and the cutscene never plays. Which wiring do you check FIRST?
- A. The RelicPlates array โ the reveal gate is RelicsThisRound >= RelicPlates.Length, so one unreachable or relic-less plate in the array means the count can never reach Length.
- B. The ReturnPortal โ the sequence cannot play while the portal is disabled.
- C. The DiveCam โ RevealHiddenWing requires every player to be in first-person.
- D. The KrakenSpawner โ SpawnedEvent must fire before the sequence is allowed to start.
- Why does a player's RelicsBanked total survive after everyone leaves and the session ends?
- A. Because athenaeum_progress is a <persistable> class stored in a module-scoped var weak_map(player, ...) โ the engine saves one record per player and reloads it next session.
- B. Because the event bus re-Signals the totals when the next session starts.
- C. Because weak_map(session, ...) automatically promotes its values to disk at round end.
- D. Because the matchmaking portal serializes device state when players travel through it.
- The round loop wraps AirSupplyCountdown() and AwaitAllRelicsBanked() in a race expression. What happens the instant the final relic is banked with 20 seconds of air left?
- A. AwaitAllRelicsBanked returns, the race completes, and the AirSupplyCountdown task is cancelled mid-Sleep โ the countdown never reaches zero.
- B. Both tasks keep running until the countdown also finishes, then the race returns.
- C. The race fails, because a race requires both tasks to complete.
- D. The countdown task keeps running in the background and ends the NEXT round early.
- The capstone code never calls a 'data layer' API, yet the hidden wing appears on victory. How?
- A. The data-layer activation is a track INSIDE the Level Sequence โ Verse just calls VaultRevealSequence.Play() and awaits StoppedEvent while the sequencer does the reveal.
- B. creative_device automatically activates all data layers when the phase enum reaches Victory.
- C. SpawnProp activates the data layer that its asset belongs to.
- D. The npc_spawner_device activates data layers listed in its character definition.
- relic_plate_sensor.OnTriggered receives MaybeAgent : ?agent and writes `if (Diver := MaybeAgent?): Game.OnRelicClaimed(Index, Diver)`. What does the `?` postfix operator do here?
- A. It's a failure-context check that unwraps the optional only if it holds a value, binding Diver to the payload and skipping the block entirely when the plate fired with no agent.
- B. It converts the ?agent into a string for the HUD message.
- C. It forces MaybeAgent to always succeed by supplying a default agent.
- D. It marks OnTriggered as <suspends> so it can await the next trigger.
Answer key
- A โ RevealHiddenWing only runs when RelicsThisRound reaches RelicPlates.Length after the race. Every plate wired into the array is part of that denominator โ an extra plate hidden behind geometry (or one with no relic spawned above it) makes the win condition unreachable, so the round always ends by air timeout instead. The portal, camera, and kraken are all downstream or independent of the reveal gate.
- A โ Persistence in Verse is exactly this pair: a <final><persistable> class (versioned, defaults on every field) plus a module-scoped var weak_map keyed by player. The engine snapshots the record per player and restores it when they rejoin โ with Use Persistent Storage enabled in island settings. Session weak_maps only live for the match, and no device or portal serializes Verse state.
- A โ race{} is west-coves' structured-concurrency tool: it resumes as soon as its FIRST task completes and cancels the rest. When RelicBankedEvent.Await() delivers the final count, AwaitAllRelicsBanked breaks and returns, so the race cancels AirSupplyCountdown wherever it is suspended. Nothing leaks into the next round โ that is the whole reason the capstone uses race instead of a shared flag.
- A โ That is the lesson-17/18 division of labor: World Partition data layers are driven by the Level Sequence's tracks (alongside the camera cuts and the kraken's control-rig keyframes), while Verse owns the timing โ Play() to start the spectacle, StoppedEvent.Await() to hand control back to gameplay. There is no direct data-layer Verse API in this flow, and none is needed.
- A โ MaybeAgent? is a failure-context unwrap: inside the `if`, it succeeds and binds Diver only when MaybeAgent actually holds an agent, and the whole if-block is skipped otherwise. That's why the sensor safely forwards Index and Diver together instead of ever calling OnRelicClaimed with a missing agent.
Recap
One step closer to Athenaeum Depths (feat. the Sunken Dive Round) โ THE capstone โ the shippable UEFN island launched from the AweShucks Town matchmaking portal
Sources
Generated by Verse Island on 2026-07-04.