Reference Devices compiles

Sea-Stack Pillars: your shared helper module

Seven lessons into the Coastal Race, your timing logic, array lookups, and debug prints are scattered across files like flotsam after a storm. This lesson extracts them into ONE shared module — RaceHelpers, with FormatTime, SafeIndex, and RaceLog — that every later lesson in the West Coves imports with a single using{} line. It is the module-building skill from Barnaby's North Jungle lessons, promoted into the SOLID layering habit the rest of the island runs on.

Updated Examples verified on the live UEFN compiler
Watch the Knotshared_helper_module in ~90 seconds.

The sea stacks off the west coves all lean on each other — pull one pillar out and the arch above it stays standing only if something else carries the load. Your race code wants the same architecture. Right now, seven lessons into the Coastal Race build, you have timing logic here, array lookups there, and Print calls scattered like barnacles. Inkbeard has dragged worse codebases to the bottom of the channel for less. Today you extract the load-bearing functions into ONE shared module — RaceHelpers — and import it from any file that needs it. You already learned what a module is with Barnaby back in the North Jungle (verse-modules and creating-custom-modules); this lesson turns that knowledge into the SOLID layering habit every later zone quietly depends on.

What you will build

The RaceHelpers module — the capstone piece every subsequent West Coves lesson imports:

  • FormatTime — turns raw seconds into a M:SS race-clock string (the countdown lesson and the finish-line ticker both call it).
  • SafeIndex — a fallback-returning array lookup, so checkpoint-name lookups never fail (builds directly on array-bounds-are-failable, lesson 6).
  • RaceLog — one shared logging door with a [CoastalRace] prefix (upgrading the raw Print habit from print-debugging-in-verse, lesson 1).

Write these once, import them everywhere. When the storm-timeout lesson, the spawn-monitor lesson, and the state-machine capstone all need a race clock, they call the SAME FormatTime — and a formatting tweak is a one-line change in one file.

Walkthrough

Step 1 — declare the module

A Verse module is a named bundle of code. Functions marked <public> inside it are callable from any file that imports the module with using { RaceHelpers }. Two rules from Barnaby's jungle lessons still rule here:

  1. <public> or it doesn't exist — a module function without <public> is internal to the module. Importers can't see it.
  2. No @editable inside a module — modules are not devices; they have no Details panel. Device references get passed in as parameters.

Step 2 — the failable math inside FormatTime

Quotient[X, Y] and Mod[X, Y] are <decides> functions from /Verse.org/Verse — they can fail (divide by zero), so Verse makes you call them with square brackets inside a failure context. We use the multi-condition if: block: if both succeed, we format the clock; if not, the fallback "0:00" survives.

Step 3 — the complete file

Here is the whole thing: the module, then a demo device that imports it and exercises all three helpers. Note the demo unwraps the ?agent from TriggeredEvent before using it — the same optional-unwrapping muscle you built in lesson 4.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
using { /UnrealEngine.com/Temporary/Diagnostics }

# RaceHelpers — the shared toolbox every Coastal Race lesson imports.
# In your project, keep this module in its own file (RaceHelpers.verse).
RaceHelpers := module:

    # Turns raw seconds into a "M:SS" race-clock string.
    # 754 seconds -> "12:34".
    FormatTime<public>(TotalSeconds : int) : string =
        var Clock : string = "0:00"
        if:
            Minutes := Quotient[TotalSeconds, 60]
            Seconds := Mod[TotalSeconds, 60]
        then:
            if (Seconds < 10):
                set Clock = "{Minutes}:0{Seconds}"
            else:
                set Clock = "{Minutes}:{Seconds}"
        Clock

    # Safe array lookup: hands back a fallback instead of failing.
    SafeIndex<public>(Names : []string, Index : int, Fallback : string) : string =
        if (Found := Names[Index]):
            Found
        else:
            Fallback

    # One shared logging door — every race message swims through here.
    RaceLog<public>(Message : string) : void =
        Print("[CoastalRace] {Message}")

# Import our own module so its helpers are in scope below.
using { RaceHelpers }

sea_stack_helper_demo := class(creative_device):

    # Wire this to a checkpoint pressure plate in the Details panel.
    @editable
    CheckpointPlate : trigger_device = trigger_device{}

    # Named gates along the channel — indexed by checkpoint number.
    CheckpointNames : []string = array{"Dock Launch", "Sea-Stack Slalom", "Kraken Channel", "Shore Sprint"}

    var CheckpointsPassed : int = 0

    OnBegin<override>()<suspends>:void =
        # Prove all three helpers work the moment the island starts.
        RaceLog("Helpers online. A 754-second run reads as {FormatTime(754)}")
        CheckpointPlate.TriggeredEvent.Subscribe(OnCheckpointPassed)

    # TriggeredEvent hands us ?agent — unwrap before using it.
    OnCheckpointPassed(Agent : ?agent):void =
        if (Racer := Agent?):
            GateName := SafeIndex(CheckpointNames, CheckpointsPassed, "Uncharted Gate")
            RaceLog("Checkpoint cleared: {GateName}")
            set CheckpointsPassed += 1

What each part does

Piece Why it matters
RaceHelpers := module: Declares the shared toolbox. In your project, put it in its own file so every device file can import it.
FormatTime<public>(...) <public> makes it visible to importers. Quotient[754, 60]12, Mod[754, 60]34"12:34".
if: ... then: The failure context both <decides> calls need. If either fails, Clock keeps its "0:00" default.
SafeIndex(...) Names[Index] is failable; the if catches the out-of-bounds case and returns Fallback instead.
RaceLog(...) One prefix, one place. When you later swap Print for a HUD message, you edit ONE function.
using { RaceHelpers } The import. Without this line, FormatTime is an unknown identifier — even in the same project.
OnCheckpointPassed(Agent : ?agent) TriggeredEvent is listenable(?agent); unwrap with if (Racer := Agent?) before use.

Drop the file into Verse Explorer, build, place the device, wire a trigger to CheckpointPlate, and step on it: the log reads [CoastalRace] Checkpoint cleared: Dock Launch, then Sea-Stack Slalom, and past the end of the array — Uncharted Gate. No crash. That's SafeIndex earning its keep.

Common patterns

Pattern 1 — a second device imports the same module

The whole point of a shared module is that other files import it too. Here a lap counter pulls in its own helper module — in a real project the module sits in its own file, and every consumer file writes its own using { lap_helpers } line (imports do NOT carry over between files).

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# In a real project this module lives in its own file —
# any .verse file in the project can then `using { lap_helpers }`.
lap_helpers := module:
    AnnounceLap<public>(LapNumber : int, LapTotal : int) : void =
        Print("Lap {LapNumber} of {LapTotal} — pull harder, sailor!")

using { lap_helpers }

lap_counter_device := class(creative_device):

    @editable
    LapPlate : trigger_device = trigger_device{}

    LapTotal : int = 3

    var CurrentLap : int = 1

    OnBegin<override>()<suspends>:void =
        LapPlate.TriggeredEvent.Subscribe(OnLapLine)

    OnLapLine(Agent : ?agent):void =
        if (Racer := Agent?):
            AnnounceLap(CurrentLap, LapTotal)
            set CurrentLap += 1

Pattern 2 — modules hold shared constants too

Remember RaceConfig from the constants lesson (lesson 3)? A module is exactly where those tuning values belong — one source of truth the whole race reads.

using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# Modules hold shared CONSTANTS too — one source of truth for tuning.
race_config := module:
    LapCount<public> : int = 3
    MinRacers<public> : int = 2
    StormTimeoutSeconds<public> : int = 300

using { race_config }

config_reader_device := class(creative_device):

    OnBegin<override>()<suspends>:void =
        Print("Race rules: {LapCount} laps, at least {MinRacers} racers")
        Print("Storm closes the channel after {StormTimeoutSeconds}s")

Where this goes next

RaceHelpers is now a permanent pillar of the Coastal Race capstone — every subsequent West Coves lesson imports it:

  • countdown-timer-loop (lesson 9) calls FormatTime for the 3-2-1-GO broadcast clock.
  • launching-logic-with-spawn (lesson 10) and the concurrency lessons log every monitor task through RaceLog.
  • The checkpoint lessons lean on SafeIndex every time a racer advances through the gate array.
  • The west-coves-coastal-race capstone (lesson 23) assembles all of it — and because every phase logged through the same door and read the same clock, debugging the full race is a RaceLog grep instead of a kraken hunt.

Extract shared logic early, import it everywhere: that is the layering habit the East Volcano's advanced systems and the Deeps' scene-graph components will assume you already have. Inkbeard approves — grudgingly, which is his warmest setting.

Build your own lesson with shared_helper_module

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →