← Back to path Download .md

πŸ‰ East Volcano

Teen 20 lessons Β· ~160 min Β· Generated 2026-07-04

REAL UEFN island (Dragon's Volcano / Emberforge β€” Cinder the dragon as greeter NPC via character device or NPC spawner with dialogue). TERRAIN/SET: volcanic cone with a bowl-shaped crater arena (the fight space), lava moat + lava veins (damage volumes), obsidian rampart wall with

Learning Outcomes

Lesson 1: Award Points in the Fire Pit β€” score_manager_device

Objectives

Student can wire a score_manager_device and award/query per-player points from Verse (Activate, SetScoreAward, GetCurrentScore).

πŸ” Builds on: center-village: trigger/button device-wiring pattern (Subscribe)

🧩 Your capstone piece: Per-elimination scoring core of Volcano Survival

Quiz

  1. Which method actually grants points to a player?
    • A. SetScoreAward
    • B. Increment
    • C. Activate
    • D. GetScoreAward
  2. How does a handler for the score manager's ScoreOutputEvent receive the player?
    • A. As a ?agent that must be unwrapped with Agent?
    • B. As a plain agent, no unwrap needed
    • C. As a player_ui
    • D. As a string
  3. When should you call the agent overloads like Activate(Player) instead of Activate()?
    • A. Never; they are deprecated
    • B. Only in OnBegin
    • C. When the device's Activating Team option targets a specific team, so the agent's team is checked
    • D. Only when reading scores
  4. What does GetScoreAward() return?
    • A. The player's total lifetime score
    • B. The number of points the next activation will award
    • C. An optional agent
    • D. Nothing; it has void return
  5. Why must Scorer be declared as an @editable field?
    • A. So it prints to the log
    • B. So it can be localized
    • C. So Verse can reference the placed device in the level and call its methods
    • D. So it auto-converts int to float
Answer key
  1. C β€” SetScoreAward/Increment/Decrement/SetToAgentScore only configure the pending amount; Activate (or Activate(Agent)) is what pays it out.
  2. B β€” ScoreOutputEvent is listenable(agent), so the handler gets a plain agent. Only optional listenable(?agent) events (like a trigger's TriggeredEvent) require unwrapping.
  3. C β€” With a specific Activating Team set, the agent's team determines whether they may affect the device, so the agent overloads are required.
  4. B β€” GetScoreAward()<transacts>:int returns the score the next Activate will grant. GetCurrentScore(Agent) returns a player's running total.
  5. C β€” Device methods can only be called through an @editable field bound to a placed device; a bare constructor instance references nothing in the level.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Per-elimination scoring core of Volcano Survival

Sources

Lesson 2: Track the Champion β€” player_reference_device

Objectives

Student can register a specific player in a player_reference_device and read/compare that agent from Verse to drive champion/target logic.

πŸ” Builds on: north-jungle: optionals (safe agent unwrap)

🧩 Your capstone piece: 'Current wave champion' tracking shown on the arena banner

Quiz

  1. Which method do you call to store a specific player in the player_reference_device so it begins tracking them?
    • A. Register(Agent)
    • B. Enable()
    • C. GetAgent()
    • D. Track(Agent)
  2. What is the correct way to call `IsReferenced` in Verse?
    • A. if (MyRef.IsReferenced[SomeAgent]):
    • B. MyRef.IsReferenced(SomeAgent)
    • C. if (MyRef.IsReferenced(SomeAgent) = true):
    • D. let result = MyRef.IsReferenced(SomeAgent)
  3. What does `Activate()` do on the player_reference_device?
    • A. It ends the round/game AND signals ActivatedEvent with the stored agent
    • B. It registers the local player as the tracked agent
    • C. It enables the device so it can accept new agents
    • D. It clears the stored agent and resets all stats
  4. What type do the player_reference_device's events (e.g., TrackedStatChangedEvent) send to their handlers?
    • A. A plain `agent` (not optional)
    • B. An optional `?agent`
    • C. A `player` type
    • D. An `int` stat value
  5. What is the difference between AgentUpdatedEvent and AgentReplacedEvent?
    • A. AgentUpdatedEvent fires on any change including first registration; AgentReplacedEvent fires only when a previously stored agent is swapped out
    • B. AgentReplacedEvent fires on any change; AgentUpdatedEvent fires only on the first registration
    • C. They are identical β€” both fire whenever Register is called
    • D. AgentUpdatedEvent fires when GetAgent is called; AgentReplacedEvent fires when Clear is called
Answer key
  1. A β€” `Register(Agent:agent)` is the method that stores an agent in the device and starts tracking their configured stat. `Enable` just turns the device on, `GetAgent` reads the stored agent, and `Track` does not exist on this device.
  2. A β€” `IsReferenced` is marked `<decides>`, making it a failable expression. In Verse, failable expressions must appear inside an `if` condition using bracket syntax: `if (MyRef.IsReferenced[SomeAgent]):`.
  3. A β€” According to the API, `Activate()` ends the round/game. It also signals `ActivatedEvent`, sending the currently stored agent to any subscribers. It does not register players, enable the device, or clear state.
  4. A β€” All five events on `player_reference_device` are typed `listenable(agent)` β€” they send a plain, non-optional `agent`. This differs from devices like `trigger_device` which send `?agent`. Your handler must be declared as `OnHandler(A : agent) : void`.
  5. A β€” AgentUpdatedEvent signals whenever the stored agent changes to a new value, including the very first registration. AgentReplacedEvent signals only when an agent that was already stored gets replaced by a different one β€” useful for detecting swaps in a bounty or leaderboard system.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” 'Current wave champion' tracking shown on the arena banner

Sources

Lesson 3: Squad Scores with Maps β€” team score tracking

Objectives

Student can maintain [team]int / [player]int score maps in Verse and keep them in sync with device-awarded points.

πŸ” Builds on: north-jungle: verse-maps + var/set fundamentals

🧩 Your capstone piece: The in-memory score model the scoreboard and leaderboard read from

Quiz

  1. Where does the actual team score live in this pattern?
    • A. In a built-in team.GetScore() method
    • B. In your own Verse var int variables that you update
    • C. Inside the hud_message_device automatically
    • D. In the trigger_device's TriggeredEvent payload
  2. How do you pass text to hud_message_device.SetText?
    • A. Pass a raw string literal directly
    • B. Call StringToMessage on the string
    • C. Use a <localizes> helper that returns a message and pass its result
    • D. Cast the string to int first
  3. TriggeredEvent hands your handler a value of what type, and what must you do with it?
    • A. An agent you can use directly
    • B. A ?agent you must unwrap with if (A := MaybeAgent?)
    • C. A team you compare with =
    • D. Nothing β€” the handler takes no parameter
  4. What does SetDisplayTime(0.0) do on a hud_message_device?
    • A. Instantly hides the message
    • B. Displays the message persistently (never auto-hides)
    • C. Throws a compile error because 0.0 is invalid
    • D. Uses the device's configured default time
  5. Which call limits how many times a treasure plate can score before you disable it?
    • A. SetResetDelay(5.0)
    • B. SetMaxTriggerCount(5) plus GetTriggerCountRemaining()
    • C. SetTransmitDelay(5.0)
    • D. ClearAllMessages()
Answer key
  1. B β€” There is no team score device β€” you keep totals in your own var ints; the trigger detects scoring and the HUD displays it.
  2. C β€” SetText takes a message. Declare Banner<localizes>(S:string):message = "{S}" and pass Banner("..."). There is no StringToMessage.
  3. B β€” TriggeredEvent is listenable(?agent); the handler receives ?agent and you unwrap it before use.
  4. B β€” Per the API, 0.0 displays the HUD message persistently. A negative value falls back to the configured device time.
  5. B β€” SetMaxTriggerCount caps how many times the trigger fires; GetTriggerCountRemaining tells you how many are left so you can Disable() it when empty.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” The in-memory score model the scoreboard and leaderboard read from

Sources

Lesson 4: Live Scoreboard on the Cannon Wall

Objectives

Student can render a live-updating scoreboard (hud_message/billboard refresh loop fed by the score map) that redraws on every score event β€” UI layering over gameplay.

πŸ” Builds on: south-shores: hud-message-device basics; north-jungle: verse-events-custom event bus

🧩 Your capstone piece: The always-on wave/score HUD layer during play

Quiz

  1. Checkpoint: your scoreboard must redraw on every score event WITHOUT polling. Which structure proves it?
    • A. A render task that does `loop: ScoreChangedEvent.Await(); RedrawBoard()` while every score mutation ends with `ScoreChangedEvent.Signal()`.
    • B. A `loop: Sleep(0.03); RedrawBoard()` task that repaints thirty times a second so no change is ever missed.
    • C. Calling `RedrawBoard()` from `OnBegin` once β€” `hud_message_device` banners refresh themselves automatically.
    • D. Subscribing `RedrawBoard` directly to `TriggeredEvent` on every device that could ever change a score.
  2. Why does the walkthrough route every score mutation through one `AddScore` function that ends with `ScoreChangedEvent.Signal()`?
    • A. So the model has a single write path and the bus a single announcer β€” any new score source (eliminations, wave clears) plugs in without touching the render loop.
    • B. Because Verse only allows one function per device to modify a `var` map.
    • C. Because `Signal()` can only be called once per game session.
    • D. So that the HUD device can also modify the score map when it redraws.
  3. What does `WallBoard.SetDisplayTime(0.0)` do, and why does this scoreboard need it?
    • A. It makes the HUD message persistent β€” the banner stays on screen until replaced, so the board reads as an always-on layer instead of a fading toast.
    • B. It disables the device so that `Show()` must be called every frame.
    • C. It sets the redraw interval of the device to 0 seconds, enabling automatic polling.
    • D. It hides the message from all players except the most recent scorer.
  4. The `TriggeredEvent` handler receives an `agent`, but the score map is `[player]int`. What is the correct bridge?
    • A. A failable cast in a failure context: `if (P := player[Scorer]):` then update `Scores[P]`.
    • B. Declare the map as `[agent]int` β€” `player` and `agent` are the same type in Verse.
    • C. Call `Scorer.AsPlayer()` which always succeeds for any agent.
    • D. Store the agent's name string as the map key instead.
  5. Predict the output: a fresh session of `cannon_wall_scoreboard`, and one raider stomps the BonusPlate once, then the ScorePlate once. What does the wall banner read after the second redraw?
    • A. `CANNON WALL | CREW 25 | BEST 25 | RAIDERS 1` β€” the ScorePlate hit is dropped because the render task was already awake.
    • B. `CANNON WALL | CREW 35 | BEST 25 | RAIDERS 2` β€” each plate counts as its own raider.
    • C. `CANNON WALL | CREW 35 | BEST 35 | RAIDERS 1` β€” 25 + 10 accumulate under the same player key, so one raider holds the top score of 35.
    • D. `CANNON WALL | CREW 10 | BEST 10 | RAIDERS 1` β€” SetText replaces the banner, so only the most recent points show.
Answer key
  1. A β€” Await() suspends the render task at zero cost until Signal() fires, so the board repaints exactly when a score changes and never in between. The Sleep loop is polling (the thing the checkpoint forbids); a one-time draw never updates; and wiring the painter to every gameplay device couples UI to gameplay instead of to the one score-changed announcement.
  2. A β€” One writer + one announcement is the architecture: gameplay sources call AddScore, AddScore updates the map and signals, and the painter never needs to know who scored or why. The other options are not Verse rules β€” and the view must never write the model.
  3. A β€” Per the hud_message_device API, a DisplayTime of 0.0 displays the message persistently. Each RedrawBoard() then replaces the pinned text in place with SetText + Show(). Without it the banner would fade after the device's default display time even when no score changed.
  4. A β€” player[Agent] is a failable cast (an agent might not be a player), so it must run in a failure context like if. player is a subtype of agent, not the same type, and there is no AsPlayer() member on agent.
  5. C β€” AddScore reads the existing entry (25 from the bonus plate), adds the new points (10), and writes 35 back under the same `player` key. RedrawBoard then sums the whole map: one entry of 35 gives CREW 35, BEST 35, RAIDERS 1. Plates are score sources, not raiders β€” the map is keyed by player, and the full-model repaint never drops or 'replaces' points.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” The always-on wave/score HUD layer during play

Sources

Lesson 5: End-of-Round Leaderboard from a Map

Objectives

Student can sort a [player]int map and present a ranked top-N leaderboard at round end.

πŸ” Builds on: north-jungle: arrays + sorting patterns; lesson 3's score map

🧩 Your capstone piece: Post-wave and game-over leaderboard screen

Quiz

  1. Which parameter type does hud_message_device's SetText require?
    • A. string
    • B. message
    • C. text
    • D. []char
  2. What DisplayTime value keeps a HUD banner on screen until you Hide it?
    • A. -1.0
    • B. 0.0
    • C. 999.0
    • D. 1.0
  3. How do you obtain a player's UI stack to add a widget?
    • A. GetPlayerUI[Player] in a failure context
    • B. GetPlayerUI(Player) as a statement
    • C. Player.UI()
    • D. GetHUD(Player)
  4. Which call actually paints a widget onto a single player's HUD?
    • A. Announcer.Show()
    • B. PlayerUI.AddWidget(Root)
    • C. SetText(...)
    • D. SetDisplayTime(0.0)
  5. How should you declare a message that interpolates live leaderboard data?
    • A. A <localizes> function returning message
    • B. A string constant
    • C. A <decides> function returning logic
    • D. A var of type message
Answer key
  1. B β€” SetText<public>(Text:message):void β€” HUD text is always a localizable `message`, not a raw string.
  2. B β€” Setting DisplayTime to 0.0 displays the HUD message persistently until Hide() or ClearAllMessages().
  3. A β€” GetPlayerUI[Player] is fallible β€” it must be bound inside an if/for, then you call AddWidget on the result.
  4. B β€” AddWidget on the player's UI stack adds the canvas/text_block widget to that player's screen.
  5. A β€” A <localizes> function like LeaderMessage(Name,Coins):message supports interpolation of {Name}/{Coins}.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Post-wave and game-over leaderboard screen

Sources

Lesson 6: Forge a Custom Stat β€” Embers Currency

Objectives

Student can define a custom tracked stat (Embers) with stat_creator_device and read/modify it from Verse as an RPG-style currency/XP.

πŸ” Builds on: lesson 1 score flow (stat vs score distinction)

🧩 Your capstone piece: The Embers currency earned per kill and spent in the Forge Vault shop

Quiz

  1. Your stat_creator_device has its Scope set to Player. Which call will succeed?
    • A. GetValueForMatch[]
    • B. GetValue[Agent]
    • C. GetValue[Team]
    • D. SetValueForMatch[10]
  2. What does a ValueChangedEvent handler receive?
    • A. A single int (the new value)
    • B. A tuple(?agent, int) β€” the optional instigator and the new value
    • C. A plain agent
    • D. A message containing the value
  3. Why must GetValue and SetValue be called inside an if/then block?
    • A. They are <suspends> and need a coroutine
    • B. They print errors otherwise
    • C. They are <decides> and can fail, so they need a failure context
    • D. Verse requires all device calls to be in if blocks
  4. What side effect does SetLevel[Agent, 3] have on the agent's Value?
    • A. It leaves the Value unchanged
    • B. It sets the agent's Value to 0
    • C. It sets the Value to Max Value
    • D. It doubles the Value
  5. How do you pass text to a device method whose parameter is typed `message`?
    • A. Pass a raw string literal directly
    • B. Call StringToMessage on a string
    • C. Use a <localizes> helper like Text<localizes>(S:string):message = "{S}"
    • D. Cast the string with message{}
Answer key
  1. B β€” With Scope = Player, only the agent overloads succeed. The Team and Match overloads fail their <decides> requirement check when scope doesn't match.
  2. B β€” ValueChangedEvent is listenable(tuple(?agent, int)). Use Payload(0)? to unwrap the optional agent and Payload(1) for the new value.
  3. C β€” These methods carry the <decides> effect β€” they can fail (e.g. wrong scope or failed requirement check), so they must be invoked in a failure context like if.
  4. B β€” Per the API, altering stat Level sets that agent's (or team's/match's) stat Value to 0.
  5. C β€” message params require localized values. Declare a <localizes> function returning message; there is no StringToMessage in Verse.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” The Embers currency earned per kill and spent in the Forge Vault shop

Sources

Lesson 7: Stat Powerups β€” Heat Resistance Drops

Objectives

Student can place/grant stat_powerup_device pickups that modify custom stats on collection, spawned between waves from Verse.

πŸ” Builds on: south-shores: spawn-prop runtime spawning pattern

🧩 Your capstone piece: Between-wave powerup drops that buff Embers/heat-resistance

Quiz

  1. Which method do you call to grant a stat powerup effect directly to a specific player without requiring them to walk over a world pickup?
    • A. Spawn()
    • B. Pickup(Agent:agent)
    • C. SetMagnitude(float)
    • D. HasEffect(Agent:agent)
  2. You call `SetMagnitude(75.0)` after a player has already picked up the powerup and the effect is running. What happens?
    • A. The running effect immediately updates to 75.0
    • B. The running effect is cancelled and reapplied at 75.0
    • C. Nothing changes for the current effect; 75.0 applies to the next pickup only
    • D. A compile error occurs because SetMagnitude is not callable at runtime
  3. What does `GetRemainingTime(Agent)` return if the agent does NOT currently have the powerup effect applied?
    • A. -1.0
    • B. The full duration configured on the device
    • C. 0.0
    • D. It fails with a runtime error
  4. How must you call `IsSpawned` in an `if` expression in Verse?
    • A. if (MyPowerup.IsSpawned()):
    • B. if (MyPowerup.IsSpawned[]):
    • C. if (MyPowerup.IsSpawned = true):
    • D. if (IsSpawned[MyPowerup]):
  5. What is the handler signature required to subscribe to `ItemPickedUpEvent` on a `stat_powerup_device`?
    • A. (Agent : ?agent) : void
    • B. (Agent : agent) : void
    • C. (Agent : player) : void
    • D. () : void
Answer key
  1. B β€” `Pickup(Agent:agent)` grants the powerup effect directly to the specified agent. `Spawn()` only places the world pickup; the player still has to collect it themselves.
  2. C β€” The API explicitly states SetMagnitude 'will not apply to any currently applied effects.' The new magnitude only takes effect for future pickups.
  3. C β€” The API returns 0.0 when the agent does not have the effect applied, and -1.0 for an infinite-duration effect. Always check HasEffect first if you need to distinguish these cases.
  4. B β€” `IsSpawned` has the `<decides>` effect, making it a failable expression. Failable expressions must be called with bracket `[]` syntax inside an `if` block in Verse.
  5. B β€” `ItemPickedUpEvent` is typed as `listenable(agent)` β€” it sends a concrete `agent`, not an optional `?agent`. Your handler must accept `(Agent : agent) : void` with no optional unwrapping needed.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Between-wave powerup drops that buff Embers/heat-resistance

Sources

Lesson 8: The Vault Remembers β€” <persistable> and [player]int Saves

Objectives

Student can declare a persistable class + weak_map(player, save) [player]-keyed var so best-wave/ember-bank survive sessions AND carry across zone islands (the cross-zone save introduced here).

πŸ” Builds on: north-jungle: classes/structs + maps

🧩 Your capstone piece: volcano_save: best wave, ember bank, unlock flags β€” read by AweShucks Town's portal hub

Quiz

  1. Which class declaration matches the canonical UEFN save pattern (volcano_save, and Santa's Toy Factory before it)?
    • A. volcano_save := class<persistable>:
    • B. volcano_save := class<final><persistable>:
    • C. volcano_save := class<final>:
    • D. volcano_save := class<unique><persistable>:
  2. Why does this fail to compile? ```verse player_info := class<persistable>: Gold:int = 0 ```
    • A. Gold needs an explicit initializer expression
    • B. Persistable classes must live in a Persistence module
    • C. A persistable class must also be marked <final>
    • D. int is not a persistable type
  3. Fill in the blank from the template: ```verse var VolcanoSaveMap:____(player, volcano_save) = map{} ```
    • A. map
    • B. player_map
    • C. session_map
    • D. weak_map
  4. Why must every read or write of `PlayerInfoMap[Player]` happen in a failable `[]` context?
    • A. The key may be absent β€” the player might not have a record yet
    • B. weak_map access can time out under load
    • C. Persistable fields are encrypted and decryption can fail
    • D. Only <suspends> functions may touch a weak_map
  5. Spot the bug β€” why is this update wrong? ```verse set VolcanoSaveMap[Player].EmberBank = VolcanoSaveMap[Player].EmberBank + 100 ```
    • A. EmberBank is <private> so it cannot be assigned
    • B. You cannot mutate a field of a stored persistable record in place β€” you must store a fresh record
    • C. weak_map values are read-only after the first write
    • D. The += operator is required for numeric fields
Answer key
  1. B β€” The persistable record is declared `class<final><persistable>`. `<final>` is REQUIRED on a persistable class β€” `<persistable>` alone won't compile, and `<final>` alone gives you no persistence.
  2. C β€” `<final>` is required on a persistable class. The tutorial's record is `class<final><persistable>`; primitives like `int` are perfectly persistable, and no special module is needed.
  3. D β€” A module-scope `weak_map(player, ...)` is the standard way to associate persistent data with a player β€” the engine persists it across sessions and releases entries when the player object is no longer referenced.
  4. A β€” weak_map reads and writes are failable because the key may be absent. That's why every accessor first ensures a record exists, e.g. `if (not PlayerInfoMap[Player]): set PlayerInfoMap[Player] = player_info{}`.
  5. B β€” You cannot mutate a field of a stored persistable record directly. The pattern rebuilds a fresh record around the change β€” override the one field and copy the rest via the copy-`<constructor>`.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” volcano_save: best wave, ember bank, unlock flags β€” read by AweShucks Town's portal hub

Sources

Lesson 9: Versioning and Resetting Save Data

Objectives

Student can version-migrate and reset persistable data safely when the save schema changes between island updates.

πŸ” Builds on: lesson 8 volcano_save class

🧩 Your capstone piece: Save-schema migration so shipped Volcano Survival updates don't corrupt player banks

Quiz

  1. In the tutorial's code, which data structure stores each player's `player_data`?
    • A. var PlayerLockers : weak_map(player, player_data) = map{}
    • B. var PlayerLockers : []player_data = array{}
    • C. var PlayerLockers : map(string, player_data) = map{}
    • D. var PlayerLockers : stack(player_data) = stack{}
  2. Which line does the tutorial put inside `OnBegin` so the reset logic runs whenever someone joins the island?
    • A. GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerJoined)
    • B. OnPlayerJoined.Await()
    • C. GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerJoined)
    • D. loop { OnPlayerJoined(GetPlayspace()) }
  3. Fill in the blank from the struct definition: `player_data := struct:` ... `Version : int = ___`. What default does the tutorial give the version 'sticker'?
    • A. 0
    • B. 1
    • C. 2
    • D. -1
  4. Predict the behavior: a brand-new player joins and has NO entry in `PlayerLockers`. What does `OnPlayerJoined` do?
    • A. It calls ResetPlayerData(P) to wipe their old page
    • B. It does nothing until the player earns a score
    • C. It crashes because PlayerLockers[P] fails
    • D. It creates a locker with default data via `set PlayerLockers[P] = player_data{}`
  5. A player already has a locker when they join. Under exactly what condition does the tutorial's `OnPlayerJoined` call `ResetPlayerData(P)`?
    • A. When CurrentData.Version = 0
    • B. Every time the player joins, unconditionally
    • C. When CurrentData.Score is greater than 0
    • D. When CurrentData.Items is not empty
Answer key
  1. A β€” The tutorial uses a `weak_map(player, player_data)` β€” the 'shared locker room' where each player keys their own data entry.
  2. C β€” `OnBegin` subscribes `OnPlayerJoined` to the playspace's `PlayerAddedEvent()`, so the handler fires every time a player joins the session.
  3. B β€” The struct declares `Version : int = 1`. Freshly-created data carries version 1; the code treats a version of 0 as 'no version set yet' and triggers a reset.
  4. D β€” The `if (CurrentData := PlayerLockers[P])` lookup fails for a new player, so the `else` branch runs and stores a fresh default `player_data{}` in their locker.
  5. A β€” The handler checks the version sticker: `if (CurrentData.Version = 0):` β€” 0 means 'no version set yet', i.e. old data, so only then is the data reset.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Save-schema migration so shipped Volcano Survival updates don't corrupt player banks

Sources

Lesson 10: The Ember Satchel β€” Build a Custom Inventory

Objectives

Student can build a Verse-managed custom inventory module (per-player item stacks in a [player] map, granter-device backed, UI readout) β€” buy/carry/spend loop.

πŸ” Builds on: north-jungle: verse-modules (ship the satchel AS an importable module); item-granter-device reference

🧩 Your capstone piece: Forge Vault shop: spend Embers on heat charms & lava totems held in the satchel

Quiz

  1. What type is the Ember Satchel itself β€” the single source of truth for who carries what?
    • A. var Satchels : [player][string]int = map{}
    • B. inventory : inventory_component
    • C. Satchels : []creative_item = array{}
    • D. var Satchels : [string]player = map{}
  2. A player with only 2 ember shards steps on the forge plate (a blade costs 3). What happens?
    • A. They lose their 2 shards but get no blade
    • B. Nothing is deducted and no blade is granted β€” TrySpend fails softly
    • C. The game crashes with a runtime error
    • D. They receive the blade on credit, going to -1 shards
  3. Why does the script call ShardGranter.GrantItem(Toucher) right after AddItem?
    • A. GrantItem is what writes the count into the Satchels map
    • B. The granter device backs the Verse ledger with a real, holdable item β€” Verse keeps the count, the device delivers the item
    • C. GrantItem is required before any map can be modified
    • D. It refreshes the HUD message device
  4. How does the HUD readout stay up to date?
    • A. A Sleep loop repaints it every half second
    • B. hud_message_device polls the map automatically
    • C. A render loop sleeps on SatchelChangedEvent.Await() and repaints only when AddItem or TrySpend calls Signal()
    • D. It repaints every time any player moves
  5. Why does AddItem write the new count with `if (set Bag[Item] = NewCount) {}`?
    • A. Because writing to a map in Verse is a failable expression, so it must run in a failure context like if
    • B. Because if statements run faster than plain statements
    • C. Because Bag is a constant and can only change inside if
    • D. Because {} deletes the old value first
Answer key
  1. A β€” The outer map keys by player (one bag each); the inner [string]int maps item names to stack counts. That nested map IS the custom inventory.
  2. B β€” TrySpend puts Have >= Amount inside the same failable if as the map lookups. If the check fails, no deduction happens and it returns false, so OnForge grants nothing.
  3. B β€” Verse is the brain, the item granter is the hand: the map tracks the stack count that game logic trusts, while GrantItem(Agent) puts the actual item into the player's Fortnite inventory.
  4. C β€” The OnBegin loop awaits the event bus, and the only two functions that change the model both Signal() it. No polling β€” the board redraws exactly when a satchel changes.
  5. A β€” Map element writes are failable in Verse. Wrapping the set in an if with an empty body is the standard pattern for a write you expect to succeed.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Forge Vault shop: spend Embers on heat charms & lava totems held in the satchel

Sources

Lesson 11: Animation Arc I β€” TeleportTo and MoveTo Basics

Objectives

Student can reposition players and props from Verse with TeleportTo[] and MoveTo (positionals, rotation, failure contexts) β€” the start of the animation arc.

πŸ” Builds on: north-jungle: failure contexts for TeleportTo[]

🧩 Your capstone piece: Teleport players into the crater arena at wave start; respawn placement

Quiz

  1. What is the difference in feel between SetLinearVelocity and ApplyLinearImpulse on a character?
    • A. SetLinearVelocity teleports instantly; ApplyLinearImpulse moves over several seconds
    • B. SetLinearVelocity replaces the character's current motion; ApplyLinearImpulse adds a physics shove on top of it
    • C. They are identical aliases for the same engine call
    • D. SetLinearVelocity only works on NPCs; ApplyLinearImpulse only works on players
  2. Why does importing BOTH /Verse.org/SpatialMath and /UnrealEngine.com/Temporary/SpatialMath in one file break a bare `vector3{...}`?
    • A. The two modules define vector3 with the same fields, causing a duplicate-symbol link error
    • B. Temporary SpatialMath is deprecated and cannot be imported alongside anything
    • C. Each module exports its own vector3 type, so the name becomes ambiguous and the compiler can't pick one
    • D. vector3 can only be constructed inside an OnBegin, not a helper function
  3. For SetLinearVelocity you build a /Verse.org/SpatialMath vector3. Which field names does it use, and which one points up?
    • A. Forward, Left, Up β€” and Up is the vertical axis
    • B. X, Y, Z β€” and Z is the vertical axis
    • C. Pitch, Yaw, Roll β€” and Yaw is up
    • D. Right, Back, Top β€” and Top is up
  4. Why is TeleportTo written with square brackets, as in `Body.TeleportTo[Pos, Rot]`?
    • A. Brackets are how you pass two arguments at once in Verse
    • B. It indexes a teleport-target array by position
    • C. TeleportTo runs on a background thread and brackets await it
    • D. TeleportTo is failable β€” it fails if the spot is blocked or out of bounds β€” so you guard it with an if
  5. A player falls off the arena and must be sent back to a checkpoint instantly. Which of the three movement verbs from this lesson fits best?
    • A. SetLinearVelocity β€” aim a velocity vector at the checkpoint
    • B. ApplyLinearImpulse β€” shove them toward the checkpoint
    • C. TeleportTo β€” the instant blink, made for checkpoints and respawns
    • D. GetFortCharacter β€” re-fetching the body resets their position
Answer key
  1. B β€” SetLinearVelocity overwrites the velocity (a running player loses forward speed and just goes up β€” a launch pad), while ApplyLinearImpulse adds to existing motion (they keep momentum and arc β€” a gust of wind).
  2. C β€” There are two distinct vector3 types. Importing both brings two `vector3` names into scope, so a bare `vector3{...}` is ambiguous. Import only the SpatialMath you need.
  3. A β€” The /Verse.org/SpatialMath vector3 uses Forward, Left, Up (the Scene Graph convention from the Animation series). Up is vertical, so an upward pop sets only Up. The X/Y/Z fields belong to the OTHER vector3 type.
  4. D β€” TeleportTo is failable: it fails when the destination is blocked or out of bounds. The [] form lets you guard it inside an if and handle the failure instead of silently doing nothing.
  5. C β€” TeleportTo is the instant blink β€” the lesson calls out checkpoints, respawns, and 'you fell, go back' as exactly its use case. Velocity and impulse change motion; they don't relocate a character instantly.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Teleport players into the crater arena at wave start; respawn placement

Sources

Lesson 12: Animation Arc II β€” MoveTo Over Time

Objectives

Student can animate a creative_prop to a target transform over a duration and sequence multi-leg moves with async MoveTo calls.

πŸ” Builds on: lesson 11 MoveTo basics

🧩 Your capstone piece: Rising-lava prop that climbs the crater between waves

Quiz

  1. Why does the code use `Entity.GetComponent[keyframed_movement_component]` with square brackets inside an `if`, rather than calling it with round brackets?
    • A. Because looking up the component can fail (the entity might not have a mover), and square brackets signal a failable call the `if` can guard
    • B. Because square brackets are required for any call that returns a component
    • C. Because round brackets would make the component read-only
    • D. Because the mover must be retrieved before the transform can be read
  2. The delta sets `Translation.Forward := 500.0`. If you place the safe in a completely different spot in the vault and run the same component, where does it end up?
    • A. At world coordinate 500 on the Forward axis, regardless of where it started
    • B. 500 units forward from wherever it was placed, because the translation is relative
    • C. It fails, because the new position has no keyframe defined for it
    • D. Back at the vault origin, since deltas reset position first
  3. What is the practical effect of swapping `ease_in_out_cubic_bezier_easing_function{}` for `linear_easing_function{}` on the glide?
    • A. The safe moves a longer distance
    • B. The safe takes less total time to arrive
    • C. The safe moves at constant speed, feeling mechanical like a conveyor instead of weighty
    • D. The safe teleports instead of gliding
  4. Why does `OnBeginSimulation` call `(super:)OnBeginSimulation()` as its first line?
    • A. To restart the animation if it was already playing
    • B. To let the base `component` run its own startup before the subclass does its work
    • C. To register the component with the editor
    • D. It is required to make `@editable` knobs appear in the Details panel
  5. Which component do you add to an entity so it can be animated by playing a list of keyframe deltas?
    • A. transform_component
    • B. keyframed_movement_component
    • C. glide_to_target_component
    • D. simulation_component
Answer key
  1. A β€” Square brackets mark a failable expression: if no Keyframed Movement component was added, the lookup fails and the `if` simply skips. `GetLocalTransform()` uses round brackets precisely because it can never fail.
  2. B β€” A delta's translation is relative to the entity's current position, so the safe always glides 500 units forward from its start point. That is why one component works on any entity without hard-coded world coordinates.
  3. C β€” Easing only shapes the speed curve, not distance or duration. Linear gives constant speed (conveyor-like); ease-in-out starts slow, speeds up, and slows to a stop, which reads as weight.
  4. B β€” Calling the super first lets the base class complete its own setup before the subclass adds behavior. The `@editable` knobs and component registration are unrelated to this call.
  5. B β€” The keyframed movement component is the purpose-built mover: it plays keyframe deltas for an entity. `glide_to_target_component` is our custom script that finds the mover and presses Play; `transform_component` just holds where the entity sits.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Rising-lava prop that climbs the crater between waves

Sources

Lesson 13: Animation Arc III β€” Ease It with Lerp

Objectives

Student can hand-roll eased motion (Lerp over a ticked timer, ease-in/out curves) instead of linear MoveTo, for juicy prop animation.

πŸ” Builds on: lesson 12 MoveTo timing; west-coves foreshadow: loop + Sleep

🧩 Your capstone piece: Eased crusher-wall and pulsing crater glow animations

Quiz

  1. What does Lerp(10.0, 20.0, 0.5) return?
    • A. 10.0
    • B. 15.0
    • C. 20.0
    • D. 0.5
  2. Why must you write `Step * 1.0 / (Steps * 1.0)` instead of `Step / Steps` when both are ints?
    • A. It runs faster
    • B. Verse does not auto-convert int to float, and int division truncates
    • C. Lerp only accepts multiplication
    • D. It avoids a Sleep error
  3. Where must the lerp animation loop using Sleep be run from an event handler?
    • A. Directly inline in the handler
    • B. Inside OnBegin only
    • C. In a coroutine via spawn { ... } because Sleep requires <suspends>
    • D. It cannot use Sleep at all
  4. What happens with Lerp(0.0, 100.0, 1.5)?
    • A. It clamps to 100.0
    • B. It errors
    • C. It extrapolates to 150.0
    • D. It returns 0.0
  5. Which timer_device event lets you speed up a lerp when time is running low?
    • A. TriggeredEvent
    • B. StartUrgencyModeEvent
    • C. EffectEnabledEvent
    • D. FailureEvent
Answer key
  1. B β€” Lerp returns From*(1-P) + To*P = 10*0.5 + 20*0.5 = 15.0, the midpoint.
  2. B β€” Lerp needs floats. Integer division would truncate to 0 for most of the loop, so you convert to float with * 1.0 first.
  3. C β€” Sleep needs a <suspends> context; spawning the loop lets the handler return immediately while the animation runs.
  4. C β€” Lerp extrapolates beyond the 0..1 range; 1.5 gives 150.0, so you should clamp or break the loop at 1.0.
  5. B β€” StartUrgencyModeEvent fires when the timer enters urgency mode, a natural cue to accelerate an animation.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Eased crusher-wall and pulsing crater glow animations

Sources

Lesson 14: Animation Arc IV β€” prop_mover_device

Objectives

Student can drive platforms/hazards with prop_mover_device (Begin/End/Advance, MovementCompleteEvent) and choose device-motion vs Verse-motion appropriately.

πŸ” Builds on: lessons 11-13 (when to graduate from code motion to the device)

🧩 Your capstone piece: Moving lava platforms and crushing obsidian walls on the Cannonball Run escape route

Quiz

  1. Which method sends the prop forward from its default configuration, ignoring any previous movement direction?
    • A. Begin()
    • B. Advance()
    • C. Reverse()
    • D. Enable()
  2. What is the correct handler signature for `AgentHitEvent`?
    • A. OnHit(Unused : tuple()) : void
    • B. OnHit(Agent : ?agent) : void
    • C. OnHit(Agent : agent) : void
    • D. OnHit() : void
  3. You want the prop to pause for 3 seconds after reaching its destination, then advance again. Where must the `Sleep(3.0)` call live?
    • A. Directly inside the `FinishedEvent` handler
    • B. Inside a separate `<suspends>` function that is `spawn`ed from the handler
    • C. Inside `OnBegin` after calling `Begin()`
    • D. Inside the `BeganEvent` handler
  4. Which event fires specifically when the prop reaches the end of its configured travel path?
    • A. EndedEvent
    • B. BeganEvent
    • C. FinishedEvent
    • D. MovementModeChangedEvent
  5. You call `SetTargetSpeed(8.0)` and then immediately call `GetTargetSpeed()`. What does `GetTargetSpeed()` return?
    • A. The original editor-configured speed, because Set doesn't take effect until Begin()
    • B. 8.0, because GetTargetSpeed reflects the value set by SetTargetSpeed
    • C. 0.0, because the prop isn't moving yet
    • D. A compile error β€” GetTargetSpeed requires <suspends>
Answer key
  1. B β€” `Advance()` always moves the prop forward based on the device's default configuration, ignoring previous movement. `Begin()` starts or resumes movement respecting the prop's current state.
  2. C β€” `AgentHitEvent` is typed `listenable(agent)` β€” it sends a plain `agent`, not an optional `?agent`. The handler must accept `agent` directly with no optional unwrapping.
  3. B β€” Event handlers are not coroutines and cannot call `Sleep`. You must `spawn` a separate function marked `<suspends>` and put `Sleep` there.
  4. C β€” `FinishedEvent` fires when the prop reaches its destination. `EndedEvent` fires when `End()` is called to stop movement early. These are distinct events.
  5. B β€” `GetTargetSpeed()` is marked `<transacts>` (not `<suspends>`) and returns the current target speed. After calling `SetTargetSpeed(8.0)`, `GetTargetSpeed()` will return `8.0`.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Moving lava platforms and crushing obsidian walls on the Cannonball Run escape route

Sources

Lesson 15: Wave Director β€” the Spawn-Wave Pattern

Objectives

Student can orchestrate timed escalating spawn waves in Verse (wave counter, per-wave size/delay tables, wave-cleared detection).

πŸ” Builds on: north-jungle: arrays (wave tables) + events; verse-npc-spawner-waves companion

🧩 Your capstone piece: The wave director loop that IS the game's spine (Wave 1..N escalation)

Quiz

  1. Why must `SpawnAt` be called inside a `spawn { }` expression when used from a `TriggeredEvent` handler?
    • A. Because `SpawnAt` has the `<suspends>` effect and event handlers are synchronous β€” calling a suspending function directly in a sync context won't compile.
    • B. Because `SpawnAt` requires an `agent` parameter that the trigger doesn't provide.
    • C. Because `spawn { }` is the only way to reference `@editable` devices.
    • D. Because `TriggeredEvent` only fires on the server and `SpawnAt` only runs on the client.
  2. What does `GetSpawnLimit()` return on a `creature_spawner_device`?
    • A. The number of creatures currently alive from this spawner.
    • B. The maximum spawn count configured in the device's Details panel.
    • C. The number of creatures eliminated so far this wave.
    • D. The number of remaining spawns before the device auto-disables.
  3. A `trigger_device` has `SetMaxTriggerCount(3)` and `SetResetDelay(10.0)` applied. What does `GetTriggerCountRemaining()` return after the trigger fires twice?
    • A. 2
    • B. 1
    • C. 3
    • D. 0, because the reset delay hasn't elapsed yet.
  4. The `EliminatedEvent` on `creature_spawner_device` sends a `device_ai_interaction_result`. How do you safely get the killing player as an `agent`?
    • A. `Result.Source` β€” it's always a valid `agent`.
    • B. `if (Killer := Result.Source?):` β€” unwrap the optional because the source may be `false` for non-agent kills.
    • C. `cast<agent>(Result.Target)` β€” the target is always the killer.
    • D. `Result.Source.GetAgent()` β€” call the helper method on the result.
  5. You want to pass wave-number text to a UI device that accepts a `message` parameter. Which approach is correct in Verse?
    • A. Pass a raw `string` literal directly: `MyUIDevice.SetText("Wave 2")`.
    • B. Use `StringToMessage("Wave 2")` from the standard library.
    • C. Declare a localizer function `WaveText<localizes>(S:string):message = "{S}"` and call `WaveText("Wave 2")`.
    • D. Cast the string: `<message>"Wave 2"`.
Answer key
  1. A β€” `SpawnAt` is marked `<suspends>`, meaning it can pause execution. Event handler callbacks are synchronous (no `<suspends>` effect), so you cannot call a suspending function directly inside them. Wrapping the call in `spawn { MyFunc() }` creates an independent async task that can suspend freely.
  2. B β€” `GetSpawnLimit()` is a `<transacts>` query that reads the static cap you set in the UEFN Details panel. It does not track live creature count or remaining spawns dynamically β€” you must maintain your own elimination counter for that.
  3. B β€” `GetTriggerCountRemaining()` returns how many more times the trigger can fire before hitting `GetMaxTriggerCount()`. With a max of 3 and 2 fires used, 1 remains. The reset delay controls the cooldown between fires, not the remaining count.
  4. B β€” `Result.Source` is typed as `?agent` (an optional). When a creature is killed by a non-agent cause (fall damage, environment), `Source` is `false`. You must use `if (Killer := Result.Source?):` to safely unwrap it before using it as a concrete `agent`.
  5. C β€” Verse has no `StringToMessage` function and raw strings are not implicitly convertible to `message`. The correct pattern is to declare a `<localizes>` function that wraps the string in a message template, then call that function wherever a `message` type is required.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” The wave director loop that IS the game's spine (Wave 1..N escalation)

Sources

Lesson 16: creature_spawner_device from Verse

Objectives

Student can control creature_spawner_device from Verse (Enable/Disable, EliminatedEvent per creature, counting kills into the score model).

πŸ” Builds on: lesson 15 wave director; lesson 1 score awards

🧩 Your capstone piece: The actual monster taps: spawner banks around the crater, kills feed Embers + score

Quiz

  1. Why must SpawnAt be called from an async context (like OnBegin or a spawn block)?
    • A. Because it is marked <suspends> β€” it takes time to load the creature before returning
    • B. Because it is marked <decides> and can fail
    • C. Because it returns void
    • D. Because it modifies an @editable field
  2. What does SpawnAt return, and how should you handle it?
    • A. An agent you can use directly
    • B. A ?agent that is false if the spawn limit was reached β€” unwrap with if (C := Maybe?)
    • C. A logic indicating success
    • D. An int count of creatures
  3. EliminatedEvent's handler receives a device_ai_interaction_result. How do you get the agent that scored the kill?
    • A. Read Result.Target directly
    • B. Use Result.Killer as an agent
    • C. Unwrap Result.Source? since it is a ?agent (false for non-agent kills)
    • D. Call Result.GetAgent()
  4. What is the difference between Disable() and EliminateCreatures()?
    • A. They are identical
    • B. Disable() stops further spawning; EliminateCreatures() removes the creatures already spawned
    • C. Disable() destroys the device permanently
    • D. EliminateCreatures() turns the faucet off but keeps creatures alive
  5. Why must the spawner be declared as an @editable field in your creative_device class?
    • A. To make it print to the log
    • B. So Verse auto-converts its int and float values
    • C. Because without the bound field, calling its methods fails with Unknown identifier
    • D. Because OnBegin requires it
Answer key
  1. A β€” SpawnAt is <suspends> since the creature needs to load; suspending calls require an async context.
  2. B β€” SpawnAt returns ?agent and yields false when the device hit its max spawn count, so you must unwrap it.
  3. C β€” Source is the killer as a ?agent and is false when a non-agent eliminated the creature, so unwrap it.
  4. B β€” Disable stops new spawns but leaves existing creatures; EliminateCreatures wipes the creatures this device spawned.
  5. C β€” You can only call a placed device's methods through an @editable field bound in the Details panel; a bare reference is an Unknown identifier error.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” The actual monster taps: spawner banks around the crater, kills feed Embers + score

Sources

Lesson 17: Tuning the Horde β€” creature_manager_device

Objectives

Student can scale creature HP/damage/speed per wave via creature_manager_device (and place set-pieces with creature_placer) for difficulty ramps.

πŸ” Builds on: lesson 16 spawners; creature-placer-device reference

🧩 Your capstone piece: Difficulty curve: tougher creature classes as waves climb

Quiz

  1. What does `creature_manager_device.Disable()` do when called in Verse?
    • A. Immediately despawns all creatures of the configured type
    • B. Stops new creatures of the configured type from spawning (existing ones may remain)
    • C. Pauses all AI behavior on the island
    • D. Destroys the device itself
  2. What type does `MatchingCreatureTypeEliminatedEvent` send to its handler?
    • A. ?agent β€” an optional agent that must be unwrapped
    • B. agent β€” a direct agent reference, no unwrapping needed
    • C. tuple() β€” no data is sent
    • D. int β€” the kill count
  3. You have Wolves and Raptors on your island. How many `creature_manager_device`s do you need to control both types independently from Verse?
    • A. One β€” a single device can manage all creature types
    • B. Two β€” one per creature type
    • C. One per creature instance placed in the level
    • D. None β€” creature management is handled automatically
  4. Which of the following is the correct way to reference a `creature_manager_device` in Verse so that calling `Enable()` actually affects placed creatures?
    • A. Declare `var CM : creature_manager_device = creature_manager_device{}` as a local variable in `OnBegin`
    • B. Declare `@editable CM : creature_manager_device = creature_manager_device{}` as a class field and wire it in the Details panel
    • C. Call `FindDeviceByTag("CreatureManager")` at runtime
    • D. Import the device with `using { /Fortnite.com/Creatures }`
  5. You want to award a score point to the player who eliminates a creature. Which event and approach is correct?
    • A. Subscribe to `MatchingCreatureTypeEliminatedEvent`; the handler receives `(Eliminator : agent)` which you pass directly to `ScoreManager.SetScoreForAgent`
    • B. Subscribe to `MatchingCreatureTypeEliminatedEvent`; unwrap `if (A := Eliminator?)` before using the agent
    • C. Poll `GetKillCount()` every second in a loop
    • D. Subscribe to `Enable()` as an event
Answer key
  1. B β€” `Disable()` prevents new spawns according to the `Affected Creatures` setting. It does not forcibly remove already-spawned creatures from the world.
  2. B β€” The event is declared as `listenable(agent)`, so the handler receives a plain `agent`. No optional unwrapping is required, unlike `creature_placer_device.EliminatedEvent` which uses `?agent`.
  3. B β€” `creature_manager_device` targets one creature type at a time. You must place one device per type and wire each to a separate `@editable` field in your Verse class.
  4. B β€” You MUST use an `@editable` field at class scope and connect it to a real placed device in the UEFN Details panel. A bare local variable or default-constructed instance has no connection to any placed device.
  5. A β€” `MatchingCreatureTypeEliminatedEvent` is `listenable(agent)` β€” the handler gets a plain `agent` with no optional wrapping, so you can pass it directly to score or other agent-accepting APIs.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Difficulty curve: tougher creature classes as waves climb

Sources

Lesson 18: Storm-Paced Rounds β€” storm_controller_device

Objectives

Student can drive a shrinking safe-zone from Verse (GenerateStorm/DestroyStorm, PhaseEndedEvent) to pace rounds and force arena movement.

πŸ” Builds on: lesson 15 wave director (storm phase per wave); north-jungle event subscription

🧩 Your capstone piece: The closing fire-ring that shrinks the safe zone every wave

Quiz

  1. What must you set on the device for your Verse GenerateStorm() call to be the thing that starts the storm?
    • A. Set 'Generate Storm On Game Start' to No
    • B. Set 'Generate Storm On Game Start' to Yes
    • C. Enable the device at game start
    • D. Nothing β€” GenerateStorm always overrides options
  2. What is the correct handler signature for PhaseEndedEvent?
    • A. OnPhaseEnded(Agent : agent) : void
    • B. OnPhaseEnded(Agent : ?agent) : void
    • C. OnPhaseEnded() : void
    • D. OnPhaseEnded(Phase : int) : void
  3. Why can't you declare your editable field as storm_controller_device directly?
    • A. It has no methods
    • B. It is marked <abstract> β€” you place a basic_ or advanced_ subclass instead
    • C. It only works with triggers
    • D. Verse forbids editable device fields
  4. Which method clears the storm to end a round?
    • A. DestroyStorm()
    • B. Disable()
    • C. EndStorm()
    • D. GenerateStorm() called twice
  5. Where should you call Storm.PhaseEndedEvent.Subscribe(OnPhaseEnded)?
    • A. At field declaration time
    • B. Inside OnBegin<override>()
    • C. Inside the handler itself
    • D. It subscribes automatically
Answer key
  1. A β€” If the storm auto-generates on game start, your GenerateStorm() call is redundant or conflicting. Set the option to No so Verse controls when it spawns.
  2. C β€” PhaseEndedEvent is listenable(tuple()), so the handler takes no parameters.
  3. B β€” storm_controller_device is the abstract base; place and reference basic_storm_controller_device or advanced_storm_controller_device.
  4. A β€” DestroyStorm() destroys the active storm; GenerateStorm() creates it.
  5. B β€” Subscriptions are wired up in OnBegin, where the device fields are valid and the suspends context exists.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” The closing fire-ring that shrinks the safe zone every wave

Sources

Lesson 19: Lava-Vent Prefab β€” Custom Scene Graph Component with Mesh/VFX/SFX

Objectives

Student can author a custom Scene Graph component bundling mesh + VFX + SFX + Verse logic into a reusable lava-vent entity, save it as a PREFAB importable by later zones.

πŸ” Builds on: north-jungle: scene-graph entities/components fundamentals; lesson 13 eased animation inside the component

🧩 Your capstone piece: Erupting lava-vent hazards scattered through the arena (the zone's exported prefab)

Quiz

  1. Checkpoint: your vent component erupts on a timer and damages agents in radius β€” which lifecycle method holds the loop?
    • A. OnSimulate β€” it is the component's only <suspends> lifecycle method, designed for long-running work
    • B. OnBeginSimulation β€” it runs first, so the loop belongs there
    • C. OnAddedToScene β€” loops must start before simulation begins
    • D. OnEndSimulation β€” it runs continuously during the simulation
  2. Why does the vent fetch its VFX with square brackets β€” `Entity.GetComponent[particle_system_component]` β€” inside an `if`?
    • A. GetComponent is a failable <decides> function: the entity might not have that component, and the if handles the failure safely
    • B. Square brackets are required for all component methods in Verse
    • C. Square brackets make the call run faster than parentheses
    • D. The if statement is needed to enable the component before use
  3. The rumble mover lives on the Plug CHILD entity, but the logic sits on the root. Which call reaches it?
    • A. Entity.FindDescendantComponents(keyframed_movement_component) β€” it searches every entity below this one in the tree
    • B. Entity.GetComponent[keyframed_movement_component] β€” GetComponent always searches children too
    • C. Entity.GetComponents() β€” it returns components from the whole scene
    • D. You cannot reach a child's components; the mover must sit on the root entity
  4. Why does the damage check convert the character's position with `(/UnrealEngine.com/Temporary/SpatialMath:)FromVector3(...)` before calling Distance?
    • A. fort_character.GetTransform() returns the old /UnrealEngine.com/Temporary/SpatialMath vector3, while the Scene Graph uses /Verse.org/SpatialMath β€” they are different types and must be converted to compare
    • B. FromVector3 converts centimeters to meters so the radius math works
    • C. Distance only accepts converted vectors for performance reasons
    • D. It rounds the position to the nearest grid cell for fair damage
  5. Ordering: inside one `Erupt()` call, which sequence do players actually experience?
    • A. Damage pulses -> rumble warning -> VFX/SFX -> VFX stops
    • B. Rumble warning -> 0.8s beat -> VFX + SFX play -> damage pulses -> VFX stops
    • C. VFX + SFX play -> rumble warning -> damage pulses -> VFX stops
    • D. Rumble warning -> damage pulses -> VFX + SFX play -> VFX stops
Answer key
  1. A β€” `OnSimulate` is the one lifecycle method with the `<suspends>` effect, so it can `Sleep` and loop for the component's whole life. The Scene Graph runs it as a task and cancels it automatically when the entity leaves the scene. `OnBeginSimulation` must return quickly β€” it is for setup and subscriptions only.
  2. A β€” `GetComponent` is declared `<reads><decides>` β€” it fails when no component of that type exists on the entity. Failable calls use square brackets and must run in a failure context like `if (...)`, so a vent missing its VFX simply skips the effect instead of crashing.
  3. A β€” `GetComponent[...]` only inspects THIS entity. `FindDescendantComponents(...)` walks the whole subtree beneath the entity, returning every match β€” so the component finds the Plug child's mover no matter how the hierarchy is arranged. That search-don't-hardcode habit is what keeps the prefab reusable.
  4. A β€” There are two SpatialMath dialects in UEFN today. Characters and devices still speak `/UnrealEngine.com/Temporary/SpatialMath`; the Scene Graph speaks `/Verse.org/SpatialMath`. Their `vector3` types do not mix, so `FromVector3` bridges the old-dialect translation into a Scene Graph `vector3` that `Distance` accepts. Calling it fully qualified avoids importing both dialects at once.
  5. B β€” `Erupt()` telegraphs first: `RumblePlug()` hops the rock, then `Sleep(0.8)` gives players a beat to run, THEN the VFX and SFX fire and the damage-pulse loop runs, and finally `VFX.Stop()` cleans up. Fair hazards warn before they hurt β€” the damage never comes before the rumble.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” Erupting lava-vent hazards scattered through the arena (the zone's exported prefab)

Sources

Lesson 20: CAPSTONE β€” Volcano Survival: The Emberpeak Trials

Objectives

Student assembles every zone lesson into one shipped wave-survival game mode with a full Lobby→Wave→Shop→Storm→GameOver state machine, persistent progress, and a playable UEFN island.

πŸ” Builds on: south-shores: enum-with-data phase machine + SpawnProp; north-jungle: event-bus + helper modules; center-village: custom-round-logic loop + HUD wiring; this zone: all of lessons 1-19

🧩 Your capstone piece: The whole game β€” every prior lesson slots into this build

Quiz

  1. Final boss: which mapping of game phase β†’ owning device/module is correct in the Emberpeak Trials?
    • A. Wave β†’ creature_spawner_device, Shop β†’ item_granter_device, Storm β†’ advanced_storm_controller_device, GameOver β†’ trigger_device + end_game_device
    • B. Wave β†’ advanced_storm_controller_device, Shop β†’ creature_spawner_device, Storm β†’ item_granter_device, GameOver β†’ button_device
    • C. Wave β†’ item_granter_device, Shop β†’ hud_message_device, Storm β†’ trigger_device, GameOver β†’ creature_spawner_device
    • D. Wave β†’ end_game_device, Shop β†’ button_device, Storm β†’ hud_message_device, GameOver β†’ advanced_storm_controller_device
  2. Why does the capstone pair the game_phase enum with a separate phase_info struct and a PhaseInfoFor lookup, instead of storing the banner text on the enum?
    • A. Verse enums cannot carry data fields, so the enum-with-data pattern keeps the enum as a label and rides the data in a companion lookup
    • B. Structs render faster than enums on the HUD
    • C. Enums with data cannot be used in case expressions
    • D. It is purely stylistic β€” Verse enums could hold the strings directly
  3. Players wipe halfway through wave 3, while RunTrials is suspended inside Bus.WaveClearedEvent.Await(). What ends the match?
    • A. AwaitDefeat wins the race: expression β€” DefeatEvent fires, the entire RunTrials task is cancelled mid-suspend, and the machine enters GameOver
    • B. The wave loop checks an if (Defeated) flag on its next iteration
    • C. The creature_spawner_device automatically activates the end_game_device
    • D. Nothing β€” the match hangs until the wave is cleared
  4. How does a player's best wave survive them logging off and returning next season?
    • A. BestWave lives in a <persistable> class stored in a module-scoped weak_map(player, ember_save), which Fortnite saves per player across sessions
    • B. The end_game_device writes it to the island's leaderboard file
    • C. The var WaveLimit field on the device is persistent by default
    • D. The hud_message_device caches the last banner it displayed
  5. In RunStormPhase, after sleeping for the storm's duration from PhaseInfoFor, which call dissolves the ash storm?
    • A. Storm.DestroyStorm()
    • B. Storm.Disable()
    • C. Storm.StopStorm()
    • D. Bus.StormEndedEvent.Signal()
Answer key
  1. A β€” Each phase has exactly one owner: creature_spawner_devices drive Wave, the item_granter_device forge owns Shop, the advanced_storm_controller_device owns Storm, and the defeat trigger_device plus end_game_device close out GameOver. Lobby belongs to the button + MatchStartEvent, and Victory to the round loop itself. Now go survive wave 5 in playtest.
  2. A β€” Verse enums are pure labels β€” they cannot hold fields. The south-shores enum-with-data pattern pairs the enum with a struct returned by an exhaustive case lookup, keeping every phase's banner and duration in one place.
  3. A β€” The whole campaign runs as one arm of race:, with AwaitDefeat as the other. When DefeatEvent signals, AwaitDefeat completes and race cancels RunTrials wherever it is suspended β€” no flag-polling needed.
  4. A β€” Persistence in Verse means a <final><persistable> class held in a module-scoped weak_map var keyed by player β€” the exact pattern from the tycoon persistence lesson. RecordWaveSurvived only overwrites when the new wave beats the stored best.
  5. A β€” The storm phase is bracketed by the advanced_storm_controller_device's own API: GenerateStorm() to raise it, Sleep for the phase duration, then DestroyStorm() to dissolve it. Verse owns the storm's whole lifecycle β€” the machine's schedule, not the island's.

Recap

One step closer to Volcano Survival: The Emberpeak Trials β€” The whole game β€” every prior lesson slots into this build

Sources

Generated by Verse Island on 2026-07-04.