Wirescript for Programmers
Before this: Tools
If you already program, most of Wirescript reads like a small typed language. This page lists what works differently, then gives a short test for every lesson so you can skip the ones you already understand.
Contents
What is different
- The program is a circuit. A compiled file is one microchip of gates
and wires, and every gate evaluates every tick, 60 times a second. There is
no
mainand no instruction pointer:@right out lit = a && bis an AND gate that is always computing. - Two contexts, pure and exec. Pure expressions are wiring: they follow
their inputs. Exec code lives in handlers and runs as a chain of steps, in
order. Writes, array reads,
Randomandforare steps, so they only exist in a handler. See Pure and Exec. letis a wire,varis storage. Aletnames a gate’s output and costs nothing. Avaris a Var gate that only handlers write. Everyvarsurvives a world save and load. Nothing resets unless you build a reset.- Two ways to read a
var.count.Valueis a pure wire holding the value as of the start of this tick. Barecountinside a handler reads it at that step, so it sees a write made earlier in the same chain. - Handlers fire on edges. A pin that only starts handlers is declared
exec(@left in pressed: exec). Wire a bool output such as a Button’s Held into it andon pressed { ... }runs once when Held turns on. Holding a button is one press.on !pressedruns when it turns off. It reads the value, so declarepressed: boolfor that. The game starts handlers too:on Clock(interval = 0.5),on ZoneEntered(zone = room) -> (character). - Handlers have no order between them. Two
onroots that fire in the same tick, a Clock included, run in no fixed order. Order exists only inside one chain. - No short-circuiting. Both sides of
&&and||are wires into one gate, so both are always computed. In a handler, a call with side effects on the right still runs when the left already decides: guard it with a nestedif. - Time is explicit.
await Sleep(_, delay = 1.0)pauses a chain, andawait hitwaits for a pulse (one that came earlier is dropped).buffer before = xisxone tick ago, and a loop of pure wires needs one. - No loops in pure code.
for i in 0..n { }is a step. It compiles to a loop plane, a chip of its own that runs every iteration within the current tick, about 100,000 iterations per tick at most. Work spread over many ticks is abuffer emitand anawait. modinlines,fncalls. A mod’s body is copied into every call: 50 gates called from 10 places is 500 gates. Afnis one callable plane with a Function Call gate per call. It reads a pin or aletonly as a parameter (WS113) and cannot return a record or an enum (WS114).- Failures are silent numbers. Divide or remainder by zero gives 0. An
unwired input reads 0, false or empty. A read past the end of an array
gives that gate’s previous value, not 0: check
.OutOfBounds, and.Foundfor a map key. - Players are keyed by user id. A death makes a new character, so
per-player state lives in a
Map<string, T>keyed byGetUserId(). - Some gates need admin. Broadcasts, chat commands, player join, leave, spawn and death events, and environment writes only load for an admin. The rest of the chip loads without them.
This chip uses most of the list:
@left in pressed: exec
@left in reset: exec
var count: int = 0
var squares: int[]
var total: int = 0
var big: int = 0
buffer before: int = count.Value
@right out sign = "count ${count.Value}, a tick ago ${before}, sum ${total.Value}, ${big.Value} over 10"
fn square(n: int) -> int {
return n * n
}
on pressed {
count += 1
squares.push(square(count))
total = squares.sum()
var n: int = 0
for s in squares {
if s > 10 {
n += 1
}
}
big = n
await Sleep(_, delay = 1.0)
count += 1
}
on reset {
count = 0
total = 0
big = 0
squares.clear()
}
squares.sum() is one gate. A for runs its body once per element, so keep
it for work no single array method does, like the count here. An array read
in pure code is an error, not a wire:
var squares: int[]
@left in i: int
@right out pick = squares[i]
Test out
Answer the questions for a lesson without looking. If you get them right, skip the lesson. Open the answers to check.
Basics
- Tools: why does
@left in on: boolfail to compile? - First Circuit: how many gates does
@right out b = amake? - Gates and Wires:
let both = a && b, then two outputsbothand!both. How many gates? - Pure and Exec: what does
on pressedfire on, level or edge? How many times while the button is held? - Remembering Values: in
on pressed { count += 1; last = count }, doeslastget the old count or the new one? - Reacting to the Game: run a handler every half second, and greet the player who walks into a zone.
- Is It Working?: name three reasons a circuit can do nothing with no error.
Answers
onis a keyword (WSP001). Pick another name.- One: the rerouter that puts
bon the chip’s right side. Without@rightit makes none, just a wire fromatob. - Two, an AND and a NOT. A
letadds no gate. - The rising edge: once per press, however long it is held.
- The new one. A bare name in a handler reads the variable at that step.
on Clock(interval = 0.5) { ... }, andon ZoneEntered(zone = room) -> (character) { ... }with azonepin.- An unwired pin, a wire to the wrong port, a stale array read, a chain
parked at an
awaitthat never resumes.
Branches
- State. Reading a Variable: in a handler,
right after
count += 1, what doescount.Valueread? Buffers: build a set/reset latch from pure wires. Variables, Resetting: what does avarhold after the world is saved and loaded? - Values. Numbers: what is
7 / 0? Lists: what doesxs[5]give on a three-element array? Maps: how do you tell a missing key from one that holds 0? Converting Values: what type doesround(2.6)return? - Logic. Choosing a Value: what does a pure
if c then x else ycompile to, and does a divide by zero in the arm not taken do harm? Toggles and Latches: a button that turns a light on and off. - Strings. Text Values: is
"0"true or false? Safe Text: why escape a display name? - Templates. Interpolation: what does
"${true}"print? Word Forms: print a bool as “Yes” or “No”. - Events. Custom Events: what does the
receiver get for a constant argument? Waiting: what
happens to a pulse that arrives before its
await? - Abstraction. Mods and
Functions: when is a
fnsmaller than amod? Imports: what happens to a library’sonhandlers underimport { name } from "lib"? - Data Shapes. Records in Storage: how many
gates does a
varholding{ x: int, y: int }cost? - Enums. Enums from Pins: what does a
matchgive for an int that names no variant? Option and Result: read the value out of anOption. - Loops. Counting Loops: how long does a
1,000-iteration
fortake? Loops Across Ticks: a back-edge loop sits inside anif. Where must itslet x: execsignal be declared? - Random. Dice: can
Random(1, 6)give 6? How do you use one roll in two places? - Input. Seat Controls: does
on input.Forward > 0.5run every tick the key is held? Remote Control: how does a circuit read the keys of a player who is not sitting anywhere? - References. Per-Player State: why not key a score map by character? Ref Values: what happens when a handler reads through an empty ref?
- Prefabs. Lifecycle and Storage: do a spawned copy and its spawner share variables? Talking to a Copy: can a copy’s handlers run before the spawner’s first event arrives?
- Admin. Admin Gates: what happens when a player without admin rights pastes a chip with one admin gate in a handler?
Answers
- State. The old value:
.Valueis as of the start of the tick, so read barecount. A latch:let state = set || (held && !reset)withbuffer held: bool = state. After a load, the value it had: build a reset. - Values. 0. The value that read gate gave last time, so check
.OutOfBounds. Afloat. Addas int. - Logic. A Select gate. Both arms compute, and the divide gives 0, so no
harm.
on pressed { lit = !lit }. - Strings. True: only
""is false. A name can contain<, which starts a tag and restyles your text. - Templates.
1.Fmt("{A:Yes=No}", flag). - Events. It is not wired, so it can arrive as 0 or empty: send a
var. It is dropped, not saved. - Abstraction. When it is called from several places and its body costs
more than calling it. A one-line body is cheaper as a
mod. They are left out (WS014): useimport "lib"to keep them. - Data Shapes. Two: one Var gate per field.
- Enums. The default: 0, or empty text. Keep the number in range with
%.if let Some(v) = r { ... }, orlet Some(v) = r else { return }. - Loops. One tick. Inside that same
if. Declared outside it, the loop runs once and stops, and the compiler warnsWS118at theawait. - Random. Yes, both ends are included. Roll once into a
letor avar. - Input. No, once when it turns on. Wire a Switch’s Character into
InputReader: the Switch keeps the last player who flipped it, and the splitter follows their keys live.GetInputsreads keys only at the step that runs it, so keep it for one-time reads, not for watching keys. - References. A respawn makes a new character, so the score starts over. The chain stops there, silently.
- Prefabs. No: each copy has its own storage, and values travel by events. Yes: guard on a value the first event sets.
- Admin. The rest of the chip loads. That handler’s chain stops at the missing gate.
Where to go next
- Syntax, Types and Statements: the language in full.
- Execution Context: pure and exec in detail.
- Best Practices: gate counts and how programs scale.
- Parts: ready-made circuits to paste and read.
- The Learn tree, for any lesson you did not test out of.