An action, in this page's sense, is something an acting block does to the running world: write a value, start or stop an animator, send a message, make or remove a part, speak, play a sound. Actions are allowed only in acting blocks, that is, in the handlers of events and in on(created, ...). A value block (a derive, a declaration, a build) may not act (subscriptions.html).
The word is also used for what a being does (hail, ask, walk); those are triggers, on visitors.html. This page is about what the world's logic does in response.
The list
| Action | What it does |
|---|---|
set(path, value) | Writes a base value once. |
start(animator) | Puts an animator into the tick; returns a handle. |
stop(handle) | Takes it out, leaving its value where it was. |
send(message, to: part) | Sends a message to a part, a being, a set of them, or the world. |
spawn(d(...), under: parent, at: ...) | Makes a copy of a design while the world runs; returns the new part. |
remove(part) | Removes a part and everything under it. |
say(speaker, words, to: being, voice: frequency) | Performs the act of speech, addressed to a being; voice:, optional, overrides the speaker's pitch for this line. |
show(element, for: d) | Shows a 2D layout element for a time (overlays.html). |
play(sound, at: ...) | Plays a sound once (proposed). |
holdIntents(b, over: d, intents: [...]) | Holds a being's movement controls for a time; returns a handle (below). |
throw(p, ...), sleep(p), wake(p) | Sets a body moving; puts it to sleep; wakes it (proposed, below). |
if c { ...; } else if d { ...; } else { ...; } | Chooses among actions, inside an acting block in braces (events.html). A functional guard if(condition, action, ...) is not used. |
play, throw and offer are outside the core list and are proposals.
set
set(path, value) set(launched, now(sim)) // the balloon set(asked[v], true) // one row of a field keyed by being set(hailing, start(together([...]))) // keep a handle in a field
Writes a base value once. The field must be declared mutable: true. A derived value is never set: to change a derived value, set one of its inputs, as the click sets launched and riding follows. Setting a value that an animator is writing is not settled; the animator would overwrite it at its next tick.
A timed step. The balloon cap's dip is a step down and, 0.25 s later, a step up. It is written as one animator of two keys jumped between:
start(keys(cap.offset, [key(sim(0 s), (0, -2 cm, 0)), key(sim(0.25 s), (0, 0, 0))], ease: steps(1)))
start and stop
let h = start(animator) stop(h)
start puts a built animator into the tick and returns a handle by which it can be stopped or waited for (ended(h), marker(h, name)). Starting an animator on a value another animator is writing replaces the older one, from the current value. stop takes an animator out and leaves its value where it was: stopping Ariadne's thread when a being leaves the labyrinth leaves the line where it was laid (animators.html). A behavior from a package is started the same way: start(walkgait.walker(body)) (behaviors.html).
A handle may be kept in a local name (let reset = together([...]), then start(reset) and ended(reset)) or in a field (set(hailing, start(...)), then on(ended(hailing), set(hailing, None))).
send
send(message, to: target) send(launch, to: target) // a path, here a field holding one (illustration) send(launch, to: island.balloon) // an absolute path send(hailed(by: self), to: v) // a being send(hailed(by: self), to: beingsWithin(self, 4 m)) send(paintLanded(at: p, by: c, struck: b), to: partsIn(court)) // everything in an area send(stormComing, to: world) // the whole world
A message has a name and a typed value, like an action. The receiver's on(message(name, ...), ...) handles it and decides what it means; the sender need not know the receiver's type. The forms of address shown are proposed.
paintLanded is an example message carrying three things: where the paint landed, who threw it and the being it struck, if any. A creature told of paint in its court could wind up to charge.
A request to an engine might be written the same way, send(testContact(blade), to: physics) (an illustration); the general request to an engine is not settled. Throwing a body, and putting it to sleep or waking it, are actions of their own (below).
spawn and remove
spawn(d(field: value, ...), under: parent, at: place, ...)
remove(part)
on(became(health < 0.5), { // illustration: a boss's army
each i in 0 ..< 20 {
spawn(troll(stance: oneOf([guard, charge, flank], seed: i)),
under: army, at: ring(self, 6 m, i / 20));
}
})
part splat // a paint splat
field opacity: fraction = 1, mutable: true, lives: client
field fading : ?handle = None
on(created, set(fading, start(transition(opacity, to: 0, over: sim(6 s), delay: sim(14 s)))))
on(ended(fading), remove(self))
spawn makes a copy of a design while the world runs, as a child of under, and returns the new part. It is the same copy a load-time declaration makes; they differ only in when. The parent owns it: removing the boss removes its army. Its name is a numbered path under the parent; whether spawn numbers copies itself is open (designs.html).
exists(p) is true while the part p is in the world, and false once it has been removed; p may be a field holding None, which does not exist. A rule that waits for a spawned part to go reads it (the looking-glass's nearness rule, exists(echo)).
remove removes a part and everything under it. A lifetime is an animator plus a handler on its end, with no separate notion of lifetime: a splat is fully opaque for 14 s, fades over the next 6 s, and is removed when the fade ends. A lifetime on spawn itself (lasts: sim(3 s)) is proposed, but is not part of the notation.
say
say(speaker, words, to: being, voice: frequency) say(g, answer(g, q), to: v) // the riddle say(g, line, to: v, voice: 118 Hz) // one line in a pitch of its own
Speech is an act; how it appears is the world's. A speaker (an NPC, a door, a radio, a player, a narrator) computes its words as a pure value, then performs the act at a moment. It puts the words nowhere. The world has a rule that answers every speech act, on(spoke(speaker, words, to: v), ...), and shows it (for a bubble, with show(element, for: d)) as its designer chose: a bubble above the speaker, a subtitle, speech from text, a chat-log line, several at once, or nothing out of earshot or through a wall. Only the act crosses the network; each visitor's machine shows it. Detail on visitors.html and overlays.html.
to: v names the addressee, not the audience. A line said by a guard is addressed to the asker, whose panel takes it as the answer to their question; but the act goes to every visitor's machine, so everyone near sees the plate over the guard's head and learns by watching that a guard refuses a second question. The asker's panel acts only for its own visitor. Nothing says who may see a line: that is the world's rule on spoke (overlays.html). voice: overrides the speaker's own pitch for one line (a speaker's voice field is its usual one). A line carries its words; the engine may compress lines on the wire, which content does not see.
play
play(sound, at: place, heardTo: distance, gain:, seed:, bus:, priority:, refDistance:, rolloff:) // proposed play(audiomixer.step(material: wood, weight: s), at: foot.heel, heardTo: 32 m) // a footstep (illustration of the form)
A one-shot sound: a footstep, a knock, a cry. Besides at: and heardTo: it takes the sound options of Sounds. play is proposed; the alternative is to spawn a voice part that removes itself (spawn(audiomixer.voice(...), under: self, at: chest)). Whether a one-shot with no identity is a short-lived child or a plain action, or both chosen by whether anything addresses it or a late arrival must hear it, is open. audiomixer is a package; its recipes and placed voices are general.
throw, sleep and wake
throw(p, from: place, aim: place or ray, speed: m/s, reach: meters, missedAt: meters,
ahead: meters = 0 m, flat: bool = false, rolling: bool = false, otherwise: direction)
sleep(p)
wake(p)
throw(ball, from: v.feet, aim: r, reach: 60 m, missedAt: 30 m, ahead: 1 m, flat: true,
speed: 13 m/s, rolling: true, otherwise: bowlingPins.lane) // the bowl
Each acts on a part that is a body (Physics: a body). throw takes the body from whatever had it (stopping an animator that was gliding it home), places it, and sets it moving toward its aim, awake; how the aim, the start and the motion are worked out is on Physics: throwing a body. sleep(p) stops the body where it is, with everything resting on or touching it, and wake(p) wakes it with everything asleep with it. The bowling green puts all its live props to sleep together once they have been nearly still for 0.8 s (Physics: acting on physics).
holdIntents
let h = holdIntents(b, over: duration, intents: [walk, turn, run, jump])
Holds a being's movement controls for a time: while it lasts, the server ignores the named intents of b, and gives them back when it ends. It blanks only the movement intents; the being's other actions, a cast among them, still work. It returns a handle, so ended(h) works on it. A newer hold on the same being replaces the older one. The zap holds its target for 1 s and the jig for 4 s (the island's server holds for the length and one step more, and the pose lasts exactly the length). This is world logic holding a being's controls; it does not make the being perform an action (Open questions).
offer
offer(ask, to: h) // proposed
Makes a world's own action available to one being: after a hail, a guard offers the questions to someone it does not take for a killer. How the browser shows the offer (a panel, four buttons, a spoken menu) is the browser's (visitors.html).
What it means
- Actions run in order, one after another, within the acting block (blocks.html).
- Only acts cross the network. World logic holds only values and ids, never a reference into the engine's memory.
- Authority. Rules that write fields living on the server or on both run only on the server; the server may refuse an action that is not available, out of range, or from a being that cannot act.
- Any action may change any part it can name. A world rule for a spell writes the target's own fields, a stored moment and a hold on its controls:
set(t.spellBegan, now(sim)); holdIntents(t, over: sim(1 s), intents: [walk, turn, run, jump]). The spell's pose is not started as an animator: it is aderiveof that stored moment, a formula,derive(t.spellPose, zapPose((now(sim) - t.spellBegan) / sim(1 s))), so every machine works out the same pose from the one moment, and a visitor who arrives mid-spell sees it from the right point with nothing to replay. A newer spell on the same target stores a newer moment and replaces the older. Sending a message and letting the receiver decide is good style where a part should manage its own state, not a rule (Events: who may change what).
How it connects
- Events: where actions are written.
- Animators: what
startstarts. - Designs: what
spawncopies. - Visitors: speech, offers, beings as targets.
- Sounds: lasting sounds as parts, one-shots as actions.
- Overlays: how a world shows a spoken line.
Open questions
- One-shot sounds: an action
play, a self-removing part, or both. - A general request to an engine, such as a contact test or a push, beyond
throw,sleepandwake. throwmeans two things: in Cone it returns an error from a function; as a world action it sets a ball rolling. Not yet reconciled.- A lifetime on
spawn. - A timed step: whether a plainer form than
keys(..., steps(1))is wanted. - May world logic make a person's being perform an action? The recommendation is no: world logic may hold a being's intents (
holdIntents, closed) or carry it, but only the person performs its actions. - Telling an actor about a refusal: silent, or a
refused(reason)the browser presents. - Live effect numbers: may an effect's numbers change once it has started?