Door, Lever and Pickup

The stateful presets: Door, Lever and Pickup.

Assumes you have finished quick-start.md and behaviors.md.

Two sections below are generated, not typed. The tables in § Fields and § Validator rules are rendered from the kit's own field and rule definitions and checked by its tests, so the values on this page cannot drift from the shipped ones.

How to read the brackets. A bracket after a statement says how it is known and on which engine: [source-read 5.x] — read in that engine's source; [measured 5.x] — observed by the kit's own tests on that engine; [unverified] — not yet confirmed.


What the three presets do

Three objects a level is actually built out of. A door opens and closes on the same press and can be locked from your own Blueprint. A lever is an N-position switch that advances one position per press. A pickup is taken once, tells the loser of a race Already taken, and then removes or respawns itself.

All three are one preset and two bound events. Every state is an encoding of the single integer the interactable already replicates, so none of them adds a byte to the wire, an RPC, or a replication test of its own.

What that costs your game:

  • No new traffic path. Nothing on this page declares a replicated property, an RPC, a multicast or a tick. Measured on the kit's byte channel, per change on a client's connection: a door press 7 B, a take 7 B, a taken key going away 18 B, coming back 12 B, and 0 B across 600 idle replication cycles with a key away — the respawn countdown is derived on every machine and never sent. All four sit inside the kit's 32 B per-change budget, and this system's own contribution to that budget is 0 B. [measured 5.8, stand-in machine, advisory]
  • The two larger rows are the ones that change phase, and the cost is not this system's. Any write that changes an interactable's phase re-stamps its phase clock — a double, eight bytes — so a resolve and a respawn cost more than a value-only change. The kit's own phase-changing control write on the same object measures 20 B, above both of them. [measured 5.8, stand-in machine, advisory]
  • Server time. The door's lock gate and the pickup's marker gate cost about 0.004 µs each. The pickup scheduler costs 0.57 µs per pickup completion and 0.28 µs per completion of anything else in the level — that second figure is what every other interactable pays for the scheduler existing at all — and the deferred write costs 0.78 µs. [measured 5.8, stand-in machine, advisory]
  • The µs figures above are advisory, and they were taken on a busy machine. They come from one engine, on a development box standing in for reference hardware, while an unrelated application held about a quarter of its memory. A quiet-machine re-measure is owed (§ Known gaps). The byte figures are unaffected — they read identically on three consecutive runs.
  • One timer owner. A taken pickup is scheduled by a single authority-side world subsystem, not by a component on every pickup actor. On a client it exists and does nothing.

Door

A slab that opens and closes on the same press, and refuses every press while it is locked. A door is two bits of the one integer the interactable replicates: bit 0 is open, bit 1 is locked. Its four fields are in § Fields.

The nodes. All three are UK Interaction|Advanced. The first two are pure reads of a value you already have in your handler.

NodeMachineWhat it answers
Is Door OpenEvery machine.bit 0 — is the door open?
Is Door LockedEvery machine.bit 1 — is it locked?
Set Door LockedAuthority only.locks or unlocks, in one state write

The two-event recipe. A station is one preset and two bound events, and a door needs only the first of them.

  • On Behavior Value Changed moves the mesh. It fires on every machine, so every player sees the door swing. Decode the value with Is Door Open, or with plain Blueprint integer arithmetic — Value AND 1 is open and Value AND 2 is locked. The two spellings are the same answer, and the demo deliberately uses the arithmetic to show that the kit's nodes are a convenience rather than a dependency (demo.md § Door).
  • On Interaction Completed is where the once-per-interaction game logic goes — the work that must never run twice. It fires on the authority only. A door usually needs nothing here, and neither does the demo's lever, which does its one write from On Behavior Value Changed behind Has Authority (§ Lever).

Which machine? On Behavior Value Changed — Every machine. Which machine? On Interaction Completed — Authority only.

Locked. A press on a locked door is refused by the door itself, on the server, and the player is told Locked — the same word a full queue gives them (lock.md § Queued, Locked, Your turn). That is deliberate: both mean not for you right now, and not because somebody is using it. The refusal arrives at your On Interaction Rejected handler with the reason Locked, the sample prompt's why-not line shows the word (prompt.md § The why-not line), and the door's value does not move.

Which machine? On Interaction Rejected — Authority only.

A client whose copy of the door already reads locked still sends the press and hears the answer one round trip later: the gate runs on the server, at press time, and the kit does not refuse locally. The predicted use cue and hold gauge start and are hidden when the answer arrives, exactly as for any refused press (protocol.md § The prediction contract). [measured 5.8]

Locking is an authority write, and the lever is the worked example. Set Door Locked writes state, so it runs on the server only. Called anywhere else it changes nothing and logs one Verbose line of its own — it never reaches the interactable's "state written on the wrong machine" Warning, because a client-side call here is a documented no-op and not a mistake. [measured 5.8] The demo's lever calls it from its On Behavior Value Changed handler, which runs on every machine, behind a Has Authority → Branch: the handle moves everywhere, and only the authority's pass writes the lock (demo.md § Lever).

A write that changes nothing does nothing: locking an already-locked door performs no state write, bumps no Serial and sends no bytes. [measured 5.8]

A heavy door. Turn Hold To Operate on and the door becomes a hold. It reads the four hold knobs from its own Hold params — hold seconds, cancel on move, the speed threshold, cooldown on cancel — and inherits the move rule unchanged (behaviors.md § Hold: the move rule). A door with HoldSeconds 2.0 and a 50 cm/s threshold completes at 2.0 s and cancels when the player walks at its own threshold, not the base class's default. [measured 5.8] A cancelled hold leaves the door exactly where it was.

The door owns bits 0–1 and reserves the rest. If you pack your own bits into a door's value with Apply State, Set Door Locked preserves them — but the next press clears them, because the press masks the value down to the two bits the door owns. A press normalises. If you need more state than the door's word, use a second component of your own rather than the door's integer.

Locking a door while somebody is holding it does not interrupt them. The gate runs at press time only, so the hold completes on the state it was admitted under and the door opens locked — value 3, a legal state of the table in § The encodings. The next press by anyone is refused. That is a decision rather than an oversight: a hold cancelled halfway by someone else's lock is a gauge the player cannot explain. [measured 5.8]

Lever

An N-position switch: one press, one position forward. A lever's BehaviorValue is the position — 0 to Position Count − 1. Its five fields are in § Fields.

NodeMachineWhat it answers
Get Lever PositionEvery machine.the position, normalised into the lever's own range

Wrap, or latch. With Wrap on (the default) the last position advances to the first, so a two-position wrapping lever is a Toggle with a better name. With Wrap off the lever latches: the press that lands on the last position also disables the interactable in the same write, so the lever stops being a focus candidate — no prompt, no press, on every machine. [measured 5.8]

Re-arming a latched lever takes two calls, in this order: Apply State back to position 0 (or wherever you want it), then Set Enabled true. Set Enabled on its own leaves the lever sitting on the last position and its very next press re-latches immediately — legal, and almost never what was meant.

A position never moves backwards except by wrapping. A value outside the lever's range — from your own Apply State, or from a restored save — reads as not a position and the next press takes it to 1. That is a reset, not a modulo: somebody who wrote 5 on a four-position lever should not silently read 1 as though they had written 1.

Editing Position Count down on an asset whose placed levers sit at a now-illegal position is safe: the seed is clamped on the next load, a live lever keeps its value until its next press normalises it, and the validator names the asset before play (UK-VAL-112, § Validator rules).

A lever holds no reference to what it operates. Feeding a gate, a door or a light is your Blueprint's job — the kit stores no target on a preset. The demo's lever moves its own handle from On Behavior Value Changed and, in the same handler behind Has Authority, locks the door at station 4 with Set Door Locked (demo.md § Lever). The handler reads the position from the event's own new value, so the handle, the label and the lock always agree.

Which machine? On Behavior Value Changed — Every machine. Which machine? Set Door Locked — Authority only. Which machine? On Phase Changed — Every machine.

A stiff lever. Turn Hold To Throw on and the lever becomes a hold, reading its own Hold params exactly as a hold-to-operate door does. A lever never refuses a press on its own account: this lever is stuck is your Set Enabled false, not a setting.

Pickup

A thing one player gets. The pickup is taken once, the loser of a race is told so in as many words, and then the object removes itself or comes back. Its three fields are in § Fields.

NodeMachineWhat it answers
Get Pickup Take CountEvery machine.how many times it has been taken
Is Pickup AvailableEvery machine.can it be taken right now?
Get Pickup Respawn RemainingEvery machine.seconds until it returns; 0 when it is not away
Get Pickup StatsAuthority only.the scheduler's counters; a client's copy reads all zeros

The take, and the word the loser hears. The value is the taken marker in bit 0 and the take count in the rest. Two players pressing in the same server frame do not both get it: the server applies the first request completely before it dispatches the second, so the second reads the marker and is refused with Already taken. That is the whole reason the marker exists — without it the loser would hear something vaguer. The winner's completion carries the item's Identity Tag to your authority-side handler, and the kit stores nothing about the item itself. [measured 5.8]

Which machine? On Interaction Completed — Authority only. Which machine? On Behavior Value Changed — Every machine. Which machine? On Phase Changed — Every machine.

Already taken, then Not available. Already taken is available for a short window only — Take Grace Seconds, 0.25 s by default — during which the key is still there and still pressable. After the window the object is gone, and a press that was already in flight is answered Not available instead. Both words come from the kit's reason table (events.md § Reasons); you type neither. The grace window is the knob that trades how long the honest word is available against how long a hidden mesh stays pressable. [measured 5.8]

Cooldown Seconds must be 0 on a pickup preset, and the validator says so. With a cooldown the completing write puts the object into cooldown before any racer can reach the behaviour, so the loser hears the cooldown's word instead of Already taken. The pickup still works — take, grace, resolve, respawn — only the race answer changes. UK-VAL-111 names the asset (§ Validator rules), and the shipped DA_Pickup ships 0. [measured 5.8]

Gone, and what gone means. When the grace elapses the object is disabled in one state write, with the marker cleared in the same update, and then, by Mode:

ModeAt the deadlineComes back
Respawn (shipped default)disabled, then a timer of Respawn Secondsyes, by the kit's timer
Destroydisabled, then the actor is destroyednever — a level restart recreates it
Disabledisabledonly when you call Set Enabled true — the chest pattern

A disabled interactable is not a focus candidate, so there is no prompt on a key that is gone, nobody can press it, and your On Phase Changed handler is where you hide and show the mesh. A player who joins while the key is away receives the phase in their first update and sees the empty disc like everybody else. [measured 5.8]

The countdown costs nothing. Get Pickup Respawn Remaining is derived on the machine that asks, from the replicated phase clock and the server's clock — so a label can count the key back on every window without a byte being sent for it. Pass the same Respawn Seconds the preset carries. Under Mode Disable it counts down to 0 and stays there: the node cannot tell the modes apart. Before a joining client has its first replicated time sample, the node reads the full Respawn Seconds rather than a number off that client's own clock — conservative on purpose, and it settles to the real remainder on the next frame. [measured 5.8] It never reads above Respawn Seconds either: a client's synced server clock can trail the moment the pickup went away by a few hundredths of a second, and the node caps the answer at the full length instead of showing more. Call it from an Actor, Component or Widget Blueprint (anything with a world): its world-context pin is hidden and fills from self, so in a plain Object, Data Asset or Function Library Blueprint the compiler warns "unsafe to call … without connecting a World Context Object" and offers no pin to connect.

Destroy mode: clients do not see the disable before the actor goes. The kit writes disabled and destroys the actor in the same server frame, so what reaches a client is the destruction, not the phase change — every replica goes away, in-flight presses answer Not available or are refused locally, and no client records the intermediate phase. [measured 5.8 — both topologies, with a probe bound to every client's replica] If you need players to see the object disable before it disappears, use Respawn or Disable and destroy it yourself later.

The respawn timer cannot tell your Disabled from its own. If you call Set Enabled false on a respawning pickup during its respawn window, the kit's timer will still fire and re-enable it. Use Mode Disable for an object you intend to control yourself, or re-disable it after the phase changes back to idle. Calling Set Enabled true early is safe and wins: the pickup is available at once and the later timer finds it already enabled and does nothing. [measured 5.8]

One more consequence of that early re-enable, worth knowing before you build on it. A pickup re-enabled by hand inside its own respawn window, and then taken a second time, is not scheduled again — the kit keeps one pending entry per pickup and that entry is still the respawn timer's. The second take leaves the object marked as taken with nothing pending, so it stays unavailable until something resets it. [measured 5.8] It is a real limit of this version, not a race: the workaround is the one above — Mode Disable, or leave the respawn window alone.

Diagnostics. Get Pickup Stats returns the server's counters — takes, Already taken refusals, resolves, destroys, disables, skipped respawns, healed markers and already-pending markers. They live on the server only. One of them is worth a caveat: the counter named Respawned increments at the resolve, before the respawn timer is armed, so it counts resolves rather than returns — its own description says otherwise and the description is what is wrong. [measured 5.8] With LogUKInteraction at Verbose the server writes one line per decision, on the machine that decided.

If a pickup is loaded already marked as taken — from a save, from your own Apply State, from seamless travel — the scheduler notices at registration and schedules the deferred write at once, so no pickup can sit answering Already taken forever with nothing pending. [measured 5.8]

The encodings

Each preset owns a total encoding of the interactable's one replicated int32. Total means every possible value has a defined reading, including values the kit never writes: a value from your own Apply State, from a restored save, or from a version of your game that packed something else in there. Every machine decodes the same way, with the same pure functions, from the same number.

Door — bit 0 is open, bit 1 is locked, and a press flips bit 0 while keeping bit 1:

Value beforeValue afterWhat changed
01closed → open
10open → closed
23locked closed → locked open
32locked open → locked closed

The last two rows are reachable only by writing the value yourself — through the protocol a locked door refuses the press before it gets here. They are defined anyway, because undefined is how a save file turns into a bug report.

Lever — the value is the position; a press advances it, wrapping or latching:

Value beforePosition CountWrapValue after
02true1
12true0
34true0
04false1

Pickup — bit 0 is the taken marker and the rest is the take count, so one press sets the marker and advances the count in a single write:

Value beforeValue afterTake count afterMarker after
031set
252set
352set
214748364610set

The last row is the count wrapping. It wraps to zero rather than overflowing into a negative number, and at a sustained eight takes a second it arrives in about four years — a correctness guarantee rather than something you will see.

The initial broadcast is not a change, and your handler must tell them apart. When a machine first receives an interactable — at level load, or when a player joins a game already in progress — the kit delivers the current value to your handler once, with the old and new values equal. That is the resting state, not a transition: set the pose, do not play the transition. A door handler that tweens on every event will tween a joining player's door open from closed, on their screen, for a door that has been open for ten minutes. Compare the two values, and animate only when they differ. [measured 5.8 — a late joiner reads the resting value of all three presets, with no re-seed]

Which machine? On Behavior Value Changed — Every machine.

Which machine?

Every member this page adds tells you which machine it runs on, in its own description in the editor and in the table below. There are only two answers.

MemberMachineTier
Is Door OpenEvery machine.Advanced
Is Door LockedEvery machine.Advanced
Get Lever PositionEvery machine.Advanced
Get Pickup Take CountEvery machine.Advanced
Is Pickup AvailableEvery machine.Advanced
Get Pickup Respawn RemainingEvery machine.Advanced
Set Door LockedAuthority only.Advanced
Get Pickup StatsAuthority only.Debug

Every machine here means the node is a pure read of a value that machine already has — the replicated state you were handed, or the server clock every machine shares. Nothing is sent when you call one, and the answer is the same everywhere.

Authority only means the call does nothing at all anywhere else. Set Door Locked writes state, which only the server may do; Get Pickup Stats reads counters only the server keeps, so a client's copy reads zeros. Neither is a mistake to call on a client — both are documented no-ops — but neither will do what you wanted.

None of these is a Simple node, and that is deliberate: the five-minute path through the kit is unchanged by this page. Everything here is reachable from the two derived events plus Get State and ordinary Blueprint integer arithmetic, which is exactly what the demo does.

Which machine? On Behavior Value Changed — Every machine. Which machine? On Interaction Completed — Authority only.

The demo uses exactly two of these nodes, and they are the worked examples. Set Door Locked is called from the lever's On Behavior Value Changed handler behind Has Authority — an every-machine event whose authority pass makes the authority-only write (demo.md § Lever). Get Pickup Respawn Remaining is read every frame by the key's label, on every machine, because the countdown is derived rather than sent (demo.md § Pickup). Everything else in that map is a Simple node.

Fields

Every field below is editable on the preset asset and read-only from Blueprint at runtime. The Range column is the hard clamp the editor enforces; the Shipped column is what the kit's own DA_Door / DA_Lever / DA_Pickup carry.

Door — FUKDoorParams

FieldTypeDefaultRangeShipped
bHoldToOperateboolfalse—false
HoldFUKHoldParamsstruct defaultssee behaviors.mdstruct defaults
bInitiallyOpenboolfalse—false
bInitiallyLockedboolfalse—false

Lever — FUKLeverParams

FieldTypeDefaultRangeShipped
PositionCountint32 (positions)22 – 162
bWrapbooltrue—true
InitialPositionint32 (positions)00 – 150
bHoldToThrowboolfalse—false
HoldFUKHoldParamsstruct defaultssee behaviors.mdstruct defaults

Pickup — FUKPickupParams

FieldTypeDefaultRangeShipped
ModeEUKPickupModeRespawnRespawn / Destroy / DisableRespawn
RespawnSecondsfloat (seconds)10.00.1 – 360010.0
TakeGraceSecondsfloat (seconds)0.250 – 20.25

Two base preset fields matter on a pickup and are not in the table above because they belong to every preset, not to this one: DA_Pickup ships CooldownSeconds at 0 (a cooldown changes what a racer is told — see UK-VAL-111) and IdentityTag at UK.Interaction.Item.Sample, which is the item id the completion event hands to your Blueprint.

The upper bound on PositionCount is the encoding's cap, UK::Interaction::Presets::LeverMaxPositions (16), not a setting. InitialPosition's upper bound is one less than that, because positions are 0-based.

Validator rules

RuleSeverityCheckedWhat it means
UK-VAL-110Warningruntime and editorA Pickup preset, or an interactable bound to one, has no Identity Tag. The hand-off carries no item id.
UK-VAL-111Warningruntime and editorA Pickup preset has Cooldown Seconds above 0. Server-first-wins is unreachable: the completing write enters Cooldown before any racer can be told Already taken. Use Respawn Seconds instead.
UK-VAL-112Warningruntime and editorA Lever preset has Initial Position at or above Position Count. It is clamped to the last position when read.
UK-VAL-113Warningruntime and editorA Door or Lever that is held to operate has a Hold Seconds above the project's Max Hold Seconds. This restates UK-VAL-060 for the two new hold hosts.

Runtime lines are written to LogUKInteraction on the authority, once per component. The editor checks run in the kit's editor validator, before Play — see validator.md.

Lite

The Lite edition strips content, never code. All three preset classes, all three behaviours, the pickup scheduler and every node on this page ship in Lite exactly as they do in the full edition.

What Lite does not ship is the three data assets: DA_Door, DA_Lever and DA_Pickup are excluded from the Lite archive by name. A Lite project that authors its own UUKPreset_Door, UUKPreset_Lever or UUKPreset_Pickup asset — a data-only asset with the fields in § Fields — gets a working door, lever or pickup with no further steps and no registration of its own.

The exclusions are written one asset at a time rather than as a folder rule, because the three stateful presets share their folder with the one preset asset Lite does ship. Whether that stays the shipped form is a packaging decision that has not been taken (behaviors.md § Lite records the same open question for the two behaviour assets).

Known gaps

Recorded so you can read every claim on this page as narrowly as it was proven.

  • Engine coverage. The three presets are written for 5.6–5.8 with no version gates, and they compile and pass their logic, reflection and structural tests on all three. The end-to-end multiplayer suite — the door, the lever, the pickup, the race, the late joiner — ran on 5.6, 5.7 and 5.8. In one line: "supported on 5.6–5.8; multiplayer end-to-end suite run on all three". Same wording as behaviors.md § Known gaps.

  • The performance figures are advisory, from one engine, on a stand-in machine — and the µs half was taken on a busy one. Every microsecond figure on this page was recorded on 5.8 on a development box standing in for reference hardware, while an unrelated application held about a quarter of its memory. They are measurements, not gates, and a quiet-machine re-measure is owed. The byte figures are unaffected: three consecutive runs returned identical rows.

  • Two players racing for a pickup has never been run across two processes. The race, the locked door with a queue and the cooldown word are all proven with two possessed players inside one process, on both a listen server and a dedicated server. The cross-process version of the join-in-progress test is written and complete, but no automated run selects it yet and no authored test map carries a door, a lever and a pickup together — so it runs nowhere and skips honestly rather than reporting a pass.

  • A player joining mid-session is proven for the first delivery only. What is measured is a client receiving all three objects at the start of its session and reading their resting values with no re-seed. The in-process rig cannot add a participant to a session already running, so "joins while the key is away" is covered by the initial delivery, not by a genuine mid-session join.

  • The post-grace refusal reaches your handler, but the sample prompt may have nothing to annotate. A press answered Not available is a refusal about an object that has just stopped being a focus candidate, so by the time the answer arrives the player often has no focused target and the sample widget's why-not line is not armed at all. The reason and its text are delivered to your On Interaction Rejected handler either way. [measured 5.8] If you want the word on screen in that case, draw it from the event rather than from the prompt.

    Which machine? On Interaction Rejected — Authority only.

  • The sample item tag has not been checked in a cooked build. DA_Pickup ships the identity tag UK.Interaction.Item.Sample, declared in C++ and verified to resolve in the editor. Whether it resolves in a cooked client or a cooked dedicated server, and whether it appears in your tag picker, has not been measured yet. If it ever reads as none, the pickup still works and the validator says so (UK-VAL-110); the fix is a configuration row in the plugin rather than a code change.

  • The editor checks are not in the Lite edition. The four rules in § Validator rules warn at runtime, on the server, once per component, in every edition. The editor checks that name the asset before you press Play run in the kit's editor validator (validator.md), which the Lite edition does not include.

  • The two-window demo walk is not signed off yet. The demo map carries all six stations and its automated tests are green; the hand-walked evidence sheet — two windows, one key, one Already taken — is written as a template and has not been performed.

  • Iris. Nothing on this page declares a replicated property, so there is nothing new for the engine's newer replication system to validate; the objects it rides on are verified on the default replication path only.