Reference Devices compiles

Min and Max in Verse: Clamping, Comparing, and Controlling Game Values

Every game needs guardrails on numbers — a health bar that never goes below zero, a score multiplier that never exceeds ten, a timer that can't run negative. Verse's built-in `Min` and `Max` functions are the cleanest way to enforce those boundaries. They work on both `int` and `float`, compile with the `<computes>` effect (meaning they're pure and side-effect-free), and are available everywhere in your Verse code without any extra imports.

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

Overview

Min and Max are pure mathematical functions built into the Verse standard library. They solve a problem that comes up constantly in game logic: you need a value that stays within a safe range.

  • Max(X, Y) returns whichever of the two values is larger.
  • Min(X, Y) returns whichever of the two values is smaller.
  • Both have overloads for int and float.
  • Both carry the <computes> effect — they are deterministic, have no side effects, and can be called anywhere (including inside <transacts> or <decides> contexts).
  • For float, if either argument is NaN, the result is NaN. Infinity is handled correctly: Max(Inf, 100.0) returns Inf.

When to reach for them:

  • Clamping health, shields, ammo, or score to a valid range (Min(CurrentHP + Heal, MaxHP)).
  • Picking the larger of two competing values (highest bidder, fastest lap time).
  • Building a manual Clamp by chaining Min and Max together.
  • Comparing two players' scores to decide a winner.

Because Min/Max have no device to place on the island, there is no UEFN device panel for them — you call them directly in Verse code.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: A Healing Station That Respects a Health Cap

A player walks into a healing zone. We want to restore 50 HP, but the player's health must never exceed their maximum of 200. We also want to make sure the heal amount is never negative (in case some other system accidentally passes a bad value). We use Min to cap the result and Max to floor the heal delta at zero.

The device references a trigger_device (the healing zone boundary) and a player_counter_device to track heals given. It reads the player's current health via fort_character, applies the clamped heal, and sets the new value.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }

# healing_station_device
# Place a trigger_device on the island and assign it to HealZone.
# Set MaxHealth and HealAmount in the Details panel.
healing_station_device := class(creative_device):

    # The trigger the player walks into to receive healing.
    @editable
    HealZone : trigger_device = trigger_device{}

    # The absolute maximum HP a player can have on this island.
    @editable
    MaxHealth : float = 200.0

    # How many HP to restore on each activation.
    @editable
    HealAmount : float = 50.0

    # Subscribe to the trigger when the game starts.
    OnBegin<override>()<suspends> : void =
        HealZone.TriggeredEvent.Subscribe(OnPlayerEnterHealZone)

    # Called whenever a player steps on / into the trigger.
    OnPlayerEnterHealZone(MaybeAgent : ?agent) : void =
        # Unwrap the optional agent — the event fires with ?agent.
        if (A := MaybeAgent?):
            # Get the fort_character so we can read/write health.
            if (Character := A.GetFortCharacter[]):
                CurrentHP   := Character.GetHealth()

                # Ensure the heal delta is never negative.
                # (Protects against a misconfigured HealAmount of -10, etc.)
                SafeHeal    := Max(HealAmount, 0.0)

                # Add the heal, but never exceed MaxHealth.
                NewHP       := Min(CurrentHP + SafeHeal, MaxHealth)

                # Apply the clamped value.
                Character.SetHealth(NewHP)

Line-by-line explanation:

Line What it does
HealZone.TriggeredEvent.Subscribe(OnPlayerEnterHealZone) Wires the trigger's event to our handler.
if (A := MaybeAgent?) Safely unwraps the ?agent the event delivers.
Character.GetHealth() Reads the player's current HP as a float.
Max(HealAmount, 0.0) Floors the heal at zero — Max returns the larger value, so a negative HealAmount becomes 0.0.
Min(CurrentHP + SafeHeal, MaxHealth) Caps the healed total — Min returns the smaller value, so we can never exceed MaxHealth.
Character.SetHealth(NewHP) Writes the clamped HP back to the character.

This is the classic clamp pattern: Min on the ceiling, Max on the floor.


Common patterns

Pattern 1 — Picking the Higher Score (int overload)

Two teams each have a score. At round end, display the winning score by picking the larger of the two with the int overload of Max.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# score_comparator_device
# Assign two score_manager_devices in the Details panel.
score_comparator_device := class(creative_device):

    @editable
    EndGameTrigger : trigger_device = trigger_device{}

    # Simulated scores — in a real project these would come from
    # score_manager_device or a shared variable.
    @editable
    TeamAScore : int = 0

    @editable
    TeamBScore : int = 0

    OnBegin<override>()<suspends> : void =
        EndGameTrigger.TriggeredEvent.Subscribe(OnRoundEnd)

    OnRoundEnd(MaybeAgent : ?agent) : void =
        # int overload — no float conversion needed.
        WinningScore := Max(TeamAScore, TeamBScore)

        # Use WinningScore to drive a HUD element, unlock a reward, etc.
        # (Here we just store it; wire it to your own display logic.)
        _ := WinningScore

Key point: The int overload of Max returns an int — no cast required. Verse does not auto-convert int to float, so keep your types consistent.


Pattern 2 — Manual Clamp for a Damage Multiplier (float, chained Min + Max)

A damage multiplier should always stay between 0.5 (half damage) and 3.0 (triple damage). Chain Max then Min to build a full clamp.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# damage_multiplier_device
# Demonstrates clamping a float to [MinMult, MaxMult] using Min + Max.
damage_multiplier_device := class(creative_device):

    @editable
    UpgradeTrigger : trigger_device = trigger_device{}

    # Boundaries for the multiplier.
    @editable
    MinMult : float = 0.5

    @editable
    MaxMult : float = 3.0

    # Current multiplier — starts at 1.0 (normal damage).
    var CurrentMult : float = 1.0

    OnBegin<override>()<suspends> : void =
        UpgradeTrigger.TriggeredEvent.Subscribe(OnUpgrade)

    OnUpgrade(MaybeAgent : ?agent) : void =
        # Each upgrade adds 0.25 to the multiplier.
        Raw := CurrentMult + 0.25

        # Clamp: first raise the floor with Max, then lower the ceiling with Min.
        Clamped := Min(Max(Raw, MinMult), MaxMult)

        set CurrentMult = Clamped

Key point: Min(Max(Value, Floor), Ceiling) is the idiomatic Verse clamp. There is no dedicated Clamp function in the standard library — this two-call chain is the canonical approach.


Pattern 3 — Choosing the Closer Spawn Point (float distance comparison)

Two spawn pads exist. When a player dies, respawn them at whichever pad is closer to the center of the map by comparing distances.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# closest_spawn_device
# Shows Min used to select the smaller of two float distances.
closest_spawn_device := class(creative_device):

    @editable
    SpawnPadA : player_spawner_device = player_spawner_device{}

    @editable
    SpawnPadB : player_spawner_device = player_spawner_device{}

    @editable
    EliminationSource : trigger_device = trigger_device{}

    # Pre-measured distances from each pad to the map centre (set in Details).
    @editable
    DistanceA : float = 1500.0

    @editable
    DistanceB : float = 2200.0

    OnBegin<override>()<suspends> : void =
        EliminationSource.TriggeredEvent.Subscribe(OnPlayerEliminated)

    OnPlayerEliminated(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            # Min returns the smaller distance — that's the closer pad.
            CloserDistance := Min(DistanceA, DistanceB)

            if (CloserDistance = DistanceA):
                SpawnPadA.SpawnPlayer(A)
            else:
                SpawnPadB.SpawnPlayer(A)

Key point: Min on floats returns the numerically smaller value. Comparing the result to each input with = lets you branch on which pad won.


Gotchas

1. int and float overloads are separate — no auto-conversion

Verse does not silently convert between int and float. Calling Max(5, 2.0) is a compile error because the two arguments have different types. Decide which type you need and be consistent:

# WRONG — mixed types
# Bad := Max(5, 2.0)   # compile error

# RIGHT — both int
GoodInt := Max(5, 2)

# RIGHT — both float
GoodFloat := Max(5.0, 2.0)

2. NaN propagates through float Min/Max

If either argument to the float overload is NaN, the result is NaN. Guard against NaN when reading values from physics or external calculations before passing them into Min/Max if correctness is critical.

3. <computes> means you can call Min/Max anywhere

Because both functions carry <computes> (a subset of <reads>, no side effects, deterministic), you can call them inside <transacts> blocks, inside <decides> expressions, and in constant initializers. You never need to worry about effect mismatches.

4. Chaining is the only clamp

There is no Clamp(Value, Min, Max) function in the Verse standard library. Always use Min(Max(Value, Floor), Ceiling). Getting the order wrong (Max(Min(...))) inverts the clamp and produces nonsensical results when Floor > Ceiling.

5. Infinity is valid input

Max(Inf, 100.0) returns Inf and Min(-Inf, 100.0) returns -Inf. This is correct IEEE 754 behavior, but watch out if you're using sentinel infinity values as "unset" placeholders — they will win every comparison.

Build your own lesson with math_min_max

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 →