Build Modules: One Pattern, Three Worlds
This is the big one. Across seven modules and a capstone you'll build the systems real islands run on — and somewhere around the third module you'll notice the secret: a guard deciding when to chase, a companion deciding how it feels, and a crop deciding when to sprout are all the SAME machine. Learn it once, build worlds with it. Every line of code in this track compiled clean on the real UEFN compiler before it reached you.
Prerequisites
- verse-basics
Learning Outcomes
- Design a state machine where concurrency IS the dispatch — race a state's timer against its triggers
- Define game items as plain data and wire them through granter, spawner, and conditional-button devices
- Build typed, capacity-guarded inventory stacks with a live UI
- Compose dependent systems — an economy that rides your inventory without owning storage
- Respect the island save budget: one shared persistent struct, never a map per system
- Assemble a complete survival game mode from the modules you built
Lesson 1: NPC State Machines
- Build the reusable NPC brain — a race-per-state machine any character can run.
Build the reusable NPC brain — a race-per-state machine any character can run.
📖 Full walkthrough: NPC State Machines
Quiz
- What is the core idea behind the NPC brain in this module?
- A. A behavior tree library
- B. Concurrency IS the state machine — dispatch on the current state, race its timer against its triggers, loop
- C. A tick-based update function
- D. A queue of commands
- Why does each state use race: between a timer and its triggers?
- A. For performance
- B. Whichever fires first WINS and the loser leg cancels — the state ends on timeout OR event, no polling
- C. Timers are unreliable alone
- D. race: is required syntax for enums
- Who is allowed to change the guard's mood?
- A. Any device that sees the player
- B. ONLY the state machine writes the mood — devices signal triggers; presentation reads
- C. The HUD
- D. The spawner device
- What is the blackboard in this design?
- A. A debug console
- B. The shared mutable state (a class instance) the FSM and watchers read/write — mood, targets, timers
- C. A UEFN device
- D. A save file
- Why is the brain pure Verse with no device calls?
- A. Devices are slow
- B. So ANY domain can reuse it — the same machine drives guards, companions, plants, and zombies; devices adapt at the edges
- C. Devices can't be imported
- D. It compiles faster
- B — The guard's whole brain is one loop: case on the current mood, race that state's own timer against its trigger events.
- B — A state that can be interrupted races its own duration against its triggers; the first to complete decides the next state.
- B — One-writer discipline: signals in, state decisions inside the machine, reads everywhere else.
- B — The blackboard class carries the brain's live state; being a class, it's a reference every leg shares.
- B — The module is a reusable brain framework; domain devices wire in through adapters, not inside the FSM.
Sources
Lesson 2: Sidekick Companions
- Carry the state machine into social AI: a mood-driven companion that follows, reacts, and bonds.
Carry the state machine into social AI: a mood-driven companion that follows, reacts, and bonds.
📖 Full walkthrough: Sidekick Companions
Quiz
- What does this module carry the state machine INTO?
- A. Vehicle AI
- B. Social AI — a mood-driven companion that follows, reacts, and bonds
- C. Weather systems
- D. Menu navigation
- How does the companion's mood change?
- A. Random rolls
- B. The FSM dispatches on the current mood and races its timer against interaction triggers — same shape as the guard
- C. The player selects it from a menu
- D. It's fixed at spawn
- What grounds this module's API usage?
- A. Guesswork from forums
- B. The live Sidekick surface of the current release — every call verified against the real digest
- C. An older tutorial
- D. Blueprint conversions
- Why does FollowOwner run as its own concurrent leg?
- A. It's faster
- B. Following is continuous and mood-independent — it runs beside the mood machine, not inside it
- C. Verse requires it
- D. It needs its own device
- What is the reaction hook for?
- A. Changing the mood directly
- B. A bounded nudge: outside events report a reaction; the brain logs it and stays in control of its own state
- C. Restarting the FSM
- D. Debug output
- B — Second world of the arc: the same race-per-state machine now drives a companion's moods instead of a guard's combat.
- B — Moods are states; petting/feeding/idling are triggers; the machine decides transitions.
- B — The module was built against the fresh Release-41.20 sidekick surface and compile-verified as a set.
- B — Locomotion and mood are separate concerns: the follow leg persists while moods cycle.
- B — Reactions inform, moods decide — outside code can't force a state, preventing forked-brain bugs.
Sources
Lesson 3: Custom Items
- Make world items your game understands — defined as data, wired through devices.
Make world items your game understands — defined as data, wired through devices.
📖 Full walkthrough: Custom Items
Quiz
- On SceneGraph, what IS an item in this module?
- A. A device with extra settings
- B. An entity wearing a small custom pickup component — the same base class Epic's native pickup rides
- C. A JSON row only
- D. A HUD element
- How do you add a new collectible item?
- A. Write a new class
- B. Add a Registry row — items are DATA (id, display name, stack cap, rarity), never new code
- C. Place a new device
- D. Fork the module
- What is the ONE acquire boundary?
- A. The HUD
- B. item_acquire_sink.Add — the single interface call every acquisition lane funnels through
- C. The spawner device
- D. A global variable
- Why does the pickup component's Sink field have NO default?
- A. A compiler bug
- B. Construction REQUIRES wiring a sink — a pickup that bypasses the boundary can't even exist
- C. Defaults are slow
- D. Style preference
- Why is Registry() a function instead of a module-scope constant?
- A. Functions are faster
- B. A module-scope archetype constant of struct type fails to compile — module scope can't hold that shape
- C. To hide the data
- D. For hot reload
- B — Each shell is an entity + pickup component subclassing basic_interactable_component, mirroring the native pickup's own shape.
- B — item_def rows in the Registry are the whole item model; adding a shell is adding a row.
- B — Scene-graph pickups and the device lane both call the sink's one Add — one door, so scoring can't drift.
- B — The one-pipeline rule made structural: Verse won't construct the component without somewhere for grants to go.
- B — A real compiler rule: the registry rebuilds per call because the constant form isn't legal Verse at module scope.
Sources
Lesson 4: Custom Inventory
- Give players typed, capacity-guarded stacks with a live pouch UI — the foundation every economy rides on.
Give players typed, capacity-guarded stacks with a live pouch UI — the foundation every economy rides on.
📖 Full walkthrough: Custom Inventory
Quiz
- What does this module give players?
- A. The native BR inventory
- B. Typed per-player stacks with capacity, a guarded Add/Remove/Count/CanAfford API, and a live pouch UI
- C. A trade market
- D. Cloud saves
- Why do sibling modules import inv_core instead of making their own maps?
- A. Maps are hard
- B. One inventory substrate for items, economy, farming — no re-derived map-of-counts anywhere (DRY as architecture)
- C. Imports are faster
- D. Epic requires it
- What is the island save-slot rule this module teaches?
- A. Save often
- B. At most TWO persistent weak_maps per island, shapes frozen once published — so systems consolidate into ONE shared player envelope
- C. One save per player
- D. Saves are unlimited
- How does this module stay persistence-SAFE by design?
- A. It saves constantly
- B. Its persistence layer is an embeddable struct behind a seam — the integrating island decides storage; the module owns none
- C. It uses three weak_maps
- D. It disables saving
- What happens when Add would exceed a stack's capacity?
- A. Silent clamp
- B. The guarded API refuses — capacity is enforced at the ONE place stacks change
- C. Overflow into a new stack
- D. A crash
- B — inv_core owns the stacks; everything reaches them through the guarded API.
- B — Every domain's stacks live in the ONE inventory; the economy literally rides it with zero wallet logic.
- B — The hard platform constraint: persistence is a shared budget; a module must not spend a slot of its own.
- B — Zero module-scope vars, zero new weak_maps: the save budget stays untouched.
- B — Guards live with the data: every mutation path runs the same checks.
Sources
Lesson 5: Economy
- Earn, spend, purchase, unlock — flows built on the inventory you just made.
Earn, spend, purchase, unlock — flows built on the inventory you just made.
📖 Full walkthrough: Economy
Quiz
- What storage does the economy own?
- A. Its own wallet map
- B. NONE — every balance is a namespaced stack in the inventory it rides; the economy ships flows, not storage
- C. A weak_map per currency
- D. A JSON ledger
- What are the economy's flows?
- A. Buy only
- B. Earn, spend, purchase, unlock, passive income — verbs over the inventory's nouns
- C. Mining and crafting
- D. Taxes
- What does a failed purchase do?
- A. Clamps to zero
- B. Simply FAILS — TryPurchase is a decides question; the else branch informs the player, no negative balances
- C. Grants anyway
- D. Throws an exception
- What makes this the arc's first DEPENDENT module set?
- A. It's the largest
- B. It hard-depends on the inventory's public surface — teaching composition: ride a substrate, inherit persistence, add only flows
- C. It has no devices
- D. It came first
- Why must the string-building helper carry <transacts>?
- A. Strings are slow
- B. String interpolation inside a transactional caller requires the helper to state its contract — the silent default fails the build
- C. It's optional style
- D. To enable localization
- B — Zero wallet logic: eco_flows delegates to inv_core; one currency home shared island-wide.
- B — The module is the verb layer: flows mutate stacks through the guarded API.
- B — Can't-afford is the failure branch of a failable call — no clamp patches anywhere.
- B — economy composes inv_core/inv_types; its build gates as a combined unit with pinned dependencies.
- B — An unannotated helper defaults no_rollback; annotate the pure builder and its transactional callers stay legal.
Sources
Lesson 6: Farming & Growing
- The same state machine, third world: plots that sprout, mature, wilt, and regrow.
The same state machine, third world: plots that sprout, mature, wilt, and regrow.
📖 Full walkthrough: Farming & Growing
Quiz
- What is the arc's third world, and what's the machine?
- A. Combat again
- B. Simulation — a plot's seed→sprout→mature cycle is the SAME race-per-state machine as the guard and companion
- C. A minigame
- D. Weather
- How does tending shorten growth?
- A. It skips states
- B. The growth race re-runs with reduced remaining time when TendEvent fires — a fresh race per iteration cancels the stale leg
- C. It doubles yield
- D. It's cosmetic
- What happens to a Mature crop nobody harvests?
- A. It stays forever
- B. Its patience races the harvest — timeout wilts it (reduced yield), harvest banks it; first to fire wins
- C. It disappears
- D. It replants itself
- How is a new crop added?
- A. A new class + new states
- B. One Registry row (timers, yields, regrows, level gate) + a rotation entry — zero new code paths
- C. A new device
- D. An engine update
- Why does this module own ZERO persistent storage?
- A. Farming shouldn't save
- B. The island save budget: progression is pure record-in/record-out transforms behind a sink — the integrating island's ONE envelope stores it
- C. It was unfinished
- D. Saves are slow
- B — Growth stages are states; timers and tend-events are the races; the pattern generalizes to any tendable lifecycle.
- B — The re-race idiom: recompute remaining and race again, so the old Sleep can't fire on a stale deadline.
- B — Mature races MatureSeconds against HarvestEvent — the guard's two-leg shape in a garden.
- B — Crops are data; the FSM and gates read the row.
- B — farm_persistent_data is an embeddable struct; the module never spends a weak_map slot.
Sources
Lesson 7: Zombie Survival
- The flagship end-to-end mode: waves, loot, and combat AI closing the loop.
The flagship end-to-end mode: waves, loot, and combat AI closing the loop.
📖 Full walkthrough: Zombie Survival
Quiz
- What makes this the arc's flagship?
- A. Most devices
- B. The first complete end-to-end game mode — waves, loot, and combat AI closing the four-world loop
- C. Best graphics
- D. It's the longest
- How do waves escalate?
- A. Hard-coded per wave
- B. As DATA — wave configs scale counts/pacing; rarity weights shift by wave number (escalation as data, not code)
- C. Random difficulty
- D. Player voting
- Why is the set standalone by v1 scope?
- A. It couldn't import
- B. Custom inventory was scoped OUT by build order — pickups route through the native granter lane; the loot pipeline is self-owned
- C. Zombies need no items
- D. It predates the arc
- The loot roll functions mirror what design?
- A. A slot machine
- B. Chance → weight → quality → grant as separate named steps — each testable, each tunable
- C. One giant function
- D. Engine defaults
- struct<concrete> on a nested config struct exists because…
- A. It looks nicer
- B. @editable requires concreteness — and the attribute belongs on the struct macro, not the type name
- C. Structs are abstract by default
- D. It enables saving
- B — Fourth world: the same machine that guarded, followed, and grew now runs a full survival mode.
- B — Escalation lives in config rows the director reads — tuning without touching logic.
- B — Honest scoping: the set compiles alone, with the swap point documented for when inventory mounts.
- B — GetLootChance/GetLootWeight/DetermineLootQuality separate the decisions the drop pipeline makes.
- B — The attribute-placement rule: s := struct<concrete>, with @editable then legal on fields of that type.
Sources
Lesson 8: Capstone: Volcano Survival
- Put it all together on Emberpeak — a full survival game mode, phase by phase.
Put it all together on Emberpeak — a full survival game mode, phase by phase.
📖 Full walkthrough: Capstone: Volcano Survival
Quiz
- 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
- 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
- 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
- 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
- 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()
- 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.
- 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.
- 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.
- 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.
- 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.