transacts: Changing Game State Safely
Tutorial beginner

transacts: Changing Game State Safely

Updated beginner Fundamentals

transacts: Changing Game State Safely

Every effect label in Verse tells you something a function is allowed to do. <computes> does pure math. <reads> looks at state but never changes it. <suspends> can pause and wait. And the one this lesson is about — <transacts> — marks a function that changes game state: setting a variable, spawning a prop, modifying a player's score. If your function alters the world, it must say so.

This sounds like bookkeeping, but it sits on top of a sharp, easily-missed fact about Verse that causes real, hard-to-debug damage: Verse does not roll back changes when something fails partway through. Understanding <transacts> is really about understanding that fact and the discipline it demands.

What <transacts> means

<!-- section-art:what-transacts-means --> transacts: Changing Game State Safely: What  means

State Change Switch

<transacts> is an effect specifier written in angle brackets after a function's parameter list. It declares: "calling me may change persistent or simulation state." set — the keyword that mutates a var — is the clearest signal you need it:

# This function changes state (set), so it must be <transacts>.
AddPoint(Player : agent)<transacts> : void =
    set Score += 1
    Print("Score is now {Score}")

A function that only reads or computes does not get <transacts>:

# Pure read — no state change — so NO <transacts>.
IsWinning(Threshold : int)<reads> : logic =
    Score >= Threshold

Real API signatures show <transacts> everywhere state changes. From the digest:

# Real signatures from Verse.digest.verse — all change state, all <transacts>.
GetRandomInt<native><public>(Low : int, High : int)<transacts> : int
(CreativeObject : creative_object_interface).GetPlayspace<native><public>()<transacts> : fort_playspace
Shuffle<public>(Input : []t where t : type)<transacts> : []t = external {}

(Even GetRandomInt is <transacts> — drawing a random number advances the world's random state, which is a state change. That detail surprises people and is a good test of whether you've understood the concept: <transacts> is about changing state, not just "writing to a variable you declared.")

<transacts> is contagious

Here is the rule that catches everyone: if your function calls a <transacts> function, your function must be <transacts> too. Effects propagate up the call chain. You cannot "hide" a state change behind a plain function — the effect leaks outward and the compiler enforces it.

# AddPoint is <transacts>. Anything that calls it inherits the obligation.
AddPoint(Player : agent)<transacts> : void =
    set Score += 1

# WRONG — calls a <transacts> function but isn't marked. Compile error.
OnGoalScored(Player : agent) : void =
    AddPoint(Player)

# RIGHT — propagate the effect upward.
OnGoalScored(Player : agent)<transacts> : void =
    AddPoint(Player)

This is the same contagion you saw with <decides> in the previous lesson. Effects are honest: a caller can't pretend it does less than the things it calls.

The big one: no rollback

In a database, a "transaction" either fully completes or fully undoes itself — that's what transactional means. Verse borrows the word but does not give you the rollback. If a <transacts> function changes three things and then a fourth step fails, the first three changes stay applied. The game is left in a half-finished, inconsistent state with no automatic rewind.

This is the single most damaging surprise in <transacts> code. Picture it:

# DANGEROUS — changes state, THEN checks a condition that might fail.
BuyItem(Player : agent, Cost : int)<transacts> : void =
    set Gold -= Cost                 # money already taken...
    if (Inventory.AddItem[Player]):  # ...but THIS can fail
        Print("Purchased")
    # if AddItem failed: gold is gone, no item, and NOTHING is undone.

The player just lost their gold and got nothing. There's no rollback to save you.

The discipline: validate first, then change

The fix is a rule you should treat as iron, straight from our project conventions: check everything first; only once every condition passes do you start changing state. Gather all the things that can fail into the validation up front, then perform the mutations with nothing left that can fail between them.

# SAFE — validate everything, THEN mutate. No half-finished state possible.
BuyItem(Player : agent, Cost : int)<transacts> : void =
    # 1) Validate: all the failable checks happen BEFORE any set.
    if (Gold >= Cost, Inventory.CanAccept[Player]):
        # 2) Mutate: now nothing here can fail partway.
        set Gold -= Cost
        Inventory.GiveItem(Player)
        Print("Purchased")
    else:
        Print("Can't buy — insufficient gold or full inventory")

This combines everything from the prior two lessons: the failable checks (Gold >= Cost, Inventory.CanAccept[Player]) live in a failure context (the if head), so they all run before the first set. The pattern generalizes:

# Project-convention shape: validate all conditions, then act.
UpdatePlayerData(Player : agent)<transacts> : void =
    if (PlayerData := GetPlayerData[Player], IsValidScore(PlayerData.Score)):
        # Every check passed — safe to perform the state changes now.
        UpdateScore(PlayerData)
        UpdateAchievements(PlayerData)

set and var

You can only set a variable declared with var. A name without var is a constant — it never changes, so reading it needs no effect and writing it is impossible. var is what creates the mutable state that makes a function <transacts> in the first place.

score_keeper := class(creative_device):

    var Score : int = 0           # mutable — can be set
    MaxScore : int = 100          # constant — cannot be set

    AddPoint()<transacts> : void =
        set Score += 1            # OK — Score is a var
        # set MaxScore += 1       # ERROR — MaxScore is constant

Putting effects together

You'll often see several effects stacked. Order doesn't matter; each adds a capability:

<transacts><decides> means "may change state and may fail." That's why you both call it with [] (failable) and accept that it touches state. The two lessons combine into one signature.

The pitfalls — what trips people up

<!-- section-art:the-pitfalls-what-trips-people-up --> transacts: Changing Game State Safely: The pitfalls — what trips people up

Irreversible State

1. Assuming rollback exists. The word "transacts" sounds like a database transaction with automatic undo. It is not. Failed changes stay applied. Internalize this — it's the root cause of "the player lost their money and got nothing" bugs.

2. Changing state before validating. Any set or state mutation that happens before a check that can still fail is a landmine. Always: validate everything, then mutate.

3. Forgetting the effect is contagious. Add a <transacts> call deep in your code and suddenly the compiler demands <transacts> on a chain of callers. That's correct — propagate it up; don't fight it.

4. Marking pure functions <transacts>. Don't add the label to functions that only read or compute. Over-marking hides which functions are actually dangerous and can force the effect needlessly up your call tree. Use <reads>/<computes> for non-mutating code.

5. Trying to set a non-var. Only var bindings are mutable. If you need to change a value, declare it var; otherwise it's a constant by design.

6. Surprise state changes. Some calls that look read-only (like GetRandomInt) are <transacts> because they advance hidden state. Trust the signature, not your intuition about the name.

Recap

  • <transacts> is the effect that says "this function changes game state." set (mutating a var) is the telltale sign you need it.
  • Effects are contagious: a function that calls a <transacts> function must be <transacts> too.
  • There is no rollback. A <transacts> function that fails partway leaves its earlier changes applied — a broken, half-finished state.
  • The discipline: validate every failable condition first (in an if/for failure context), then perform all the state changes with nothing failable in between.
  • Only var values can be set; constants can't. Stacked effects like <transacts><decides> combine "changes state" with "can fail."

References

Get the complete code — free

You've read the full walkthrough. The complete, copy-paste-ready Verse solution is free for members — sign in to unlock it.

Free with your BrainDead.TV / BrainDeadGuild Discord account. The walkthrough above is always free.

Check your understanding

Test yourself with an interactive quiz and track your progress + earn XP — free for members.

Up next · Optionals, Failure & Modules Verse Utility Functions: The Built-in Toolbox Continue →

Turn this into a guided course

Add Verse <transacts> effect — what it marks, the set keyword and var mutability, effect contagion, the no-rollback rule, and the validate-first-then-mutate discipline to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.

Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.

Comments

    Sign in to vote, comment, or suggest an edit. Sign in