An event says: when this happens, do that, once. Dusk comes, and the lamps start to fade up. A cap is clicked, and the balloon takes off. A visitor asks a guard a question, and the guard answers. In a program a function runs when another function calls it; in a world nothing calls the lantern. Its logic says what it is watching for and decides, when that happens, whether to act. The thing watched for is the trigger; what is done is the handler, an acting block.
How you write it
on(trigger, action)
on(trigger, { ...an acting block in Cone's form... })
on takes one action, or one acting block that does several, in order.
on(dusk, start(transition(lampLevel, to: 1, over: world(15 min), ease: linear, from: current, begin: dusk.due))) on(dawn, start(transition(lampLevel, to: 0, over: world(15 min), ease: linear, from: current, begin: dawn.due)))
When the world's day reaches dusk, the lamp level fades in a straight line from wherever it is to 1 over a quarter of an hour of the world's day (15 s of simulation time when the day lasts 24 minutes, Values: the day length), counted from the moment dusk was due, not from when the message was read; a clock set forward past dusk finds the lamps already lit. A dusk while the lamps are on changes nothing.
Triggers
| Trigger | Fires when | Carries |
|---|---|---|
| Messages from the browser's vocabulary | ||
dusk, dawn | The world's day clock passes the hour the sky gives, sky.duskHour or sky.dawnHour (Sky and weather). | The moment it was due: dusk.due. |
clicked(part) | Any visitor clicks the part: the click picks the nearest part its ray from the eye meets, at any distance, so a part hidden behind another cannot be clicked (the balloon's cap, behind the basket's walls, only from inside). | The part. |
entered(p), left(p) | A being comes into or goes out of part p's area, declared by a call in the part's body, area(self, volume) (Physics: areas, one mechanism for triggers, zones, portal mouths and waking regions, proposal). | The part; the being, written by: v (proposed). |
created | The part has just been made, at load or by a spawn (loading.html). | — |
hardLanding(b, speed:, cost:) | A being lands hard (Physics: the built-in numbers). | The being b, its landing speed and the cost to its health, bound by the handler: on(hardLanding(b, speed: s, cost: c), ...). |
struck(p, by: q) | The body p, asleep, is woken by a touch from a moving body q, however lightly; bodies asleep together with it wake and are struck too (Physics: a body's events, proposal). | The body; the moving body that touched, by: q. |
cameToRest(p) | The body p falls asleep: by the engine's own stillness rule, or put to sleep with sleep(p). | The body. |
| Actions by beings (visitors.html) | ||
action(name, value, to: target, by: v) | A being performs a named action: a visitor through their input, a villager through its mind. | The value it carries, the part or place it is aimed at, the being that acted. |
| Messages from parts | ||
message(name, from: p) | Another part, or a world rule, sends the message (actions.html, send). | The message's typed value; its sender. |
| An animator's own events (animators.html) | ||
ended(h) | The animator with handle h finishes, is stopped, or is superseded. | — |
marker(h, name) | The animator passes a named moment, such as a clip's mark. | — |
| Conditions | ||
became(c) | The condition c turns true. | — |
held(c, for: d) | The condition has been true for the duration d without a break. | — |
A message is its own trigger. A world declares a message by sending it, and the message's name and its typed value are the trigger: send(zapLanded(on: t), to: world) raises it for the whole world, and on(zapLanded(on: b), ...) handles it, with b bound to the value it carried. The two are a pair. message(name, from: p) is the same trigger when the handler needs the sender.
The spoken word has its own trigger, spoke(speaker, words, to: v), which the world answers to show speech (visitors.html). Further engine messages, contact and covered, and a seat's seated, are proposed.
Conditions: became and held
A condition is a trigger. became(c) fires once, at the moment c turns true; held(c, for: d) fires once c has held that long. The engine watches a condition only when its inputs change, and fires only when the condition changes, never every tick. A condition held for a time is a small state machine (waiting, counting, fired) that the content should not have to write; held says it in one word.
// The looking-glass opens
each v in visitors { // one watch for each visitor
on(held(still(v) && inFront(v, of: glass, from: 0.6 m, to: 5.5 m, across: 2.5 m)
&& facing(v, toward: glass, within: 45.57 deg),
for: sim(3 s)),
set(open, true))
}
// ... and closes once nobody has been near for 10 s
on(held(open && !near, for: sim(10 s)), set(open, false))
A condition over beings is written each v in visitors around the rule, Cone's loop: it makes one rule for each visitor, and v is that visitor inside it.
Three conditions about a being are defined here, with the island's numbers:
still(b)is true whileb's speed is under 0.05 m/s, it is on the ground, and it is not walking (it is not pushing forward).inFront(b, of: pane, from:, to:, across:)is true whilebis on the side the pane faces, betweenfromandtoout from its plane, and no more thanacrossto either side of its middle. The looking-glass uses 0.6 m, 5.5 m and 2.5 m. It measures along the pane's own axes and says nothing of height, so the looking-glass's own rule also asksonIsland(v)(above y = -150 m): a being in the labyrinth 300 m below is not in front of the glass.facing(b, toward: p, within: angle)is true while the forward part ofb's heading, towardsp, is above the cosine ofangle. The island uses a cosine of 0.7, which iswithin: 45.57 deg.
There is no time-based while, for now. (Cone's while loop, a loop inside a block, is a different thing; that the two share a word is listed under Open questions.) "While X, do Y" is written as an entry and an exit, on(became(c), ...) and on(became(!c), ...), or as a subscription when Y is a value, as the lanterns' nearness is. A while that expands to the pair may be added once it is clear how far the pair goes: swimming while deep enough, a control usable while inside a circle of influence.
Handlers are acting blocks
What follows the trigger is an acting block. It may calculate first, under local names, and then act; its actions are done in order (blocks.html). A single action may follow the trigger directly, on(dusk, start(...)); anything more is a block in braces, with semicolons, and Cone's if, else if, else and match choose among actions.
// The guards' riddle
on(action(ask, q, to: g, by: v), {
if within(v, g, 4 m) {
if asked[v] {
say(g, "You have had your question.", to: v);
} else {
set(asked[v], true);
say(g, answer(g, q), to: v);
}
}
})
A visitor within 4 m asks one of four questions; the guard answers once, and to any second question says "You have had your question." A functional guard, if(condition, action, ...), has no else: this reply would need two guards that each tested asked[v], and their order would matter; a six-way reply written as a chain of guards could fire two branches. Cone's else and match remove that trap, and the functional guard is not used.
Calculate and kick off
"Calculate something and kick something off" needs no third kind of block. It is two lines: a derive that calculates, and an on(became(...)) watching it (subscriptions.html). The calculation stays pure; the rule fires once, however often the value is recalculated.
A guard against the default
Starting an animator on a value that another animator is writing replaces the older one, from the current value (animators.html). Where that is wrong, the handler says so with a guard. The balloon ignores a click during its ride; a being ignores a second hail while it is hailing:
on(clicked(cap), {
start(keys(cap.offset, [key(sim(0 s), (0, -2 cm, 0)), key(sim(0.25 s), (0, 0, 0))], ease: steps(1)));
if !riding {
set(launched, now(sim));
start(ride);
}
})
Every click dips the cap 2 cm for a quarter of a second, even one the balloon ignores; a click during the dip starts a new one that takes over. Whether a guard is how every exception to "the newest takes over" is written is open.
What a handler returns
A response returns nothing by default. Two cases may want a returned value:
- A reply to a question. Logic asking another part and using its answer:
let a = ask(guard, whichDoor);(an illustration). The recommendation is that a reply be a message back to the sender, tied to the one it answers, so that nothing ever waits across the network. - Whether the event was handled. A click on the balloon's cap should not also run a click rule the balloon itself might have (an illustration). Without "handled", every enclosing part that listens for clicks would hear every click inside it.
What it means
Who receives an event
- Vocabulary messages go to every part that handles them: the world clock's engine delivers
dusk; nothing in the content calls it. - A rule written on a design runs per copy; one reading and writing only common fields runs once (designs.html).
- An action aimed at a part with no handler passes to its parent: the riddle's
askis aimed at a guard, but the guard type has no handler for it, so it reaches the hall, which holds both guards and the liar. The recommendation: a message passes up only when unhandled, on up to the world, then is ignored. - World rules are events on the world, or on a region part such as an arena, and apply within it. A rule for every scene is on the world itself; a rule for one scene is on that scene (Worlds, scenes and regions).
When it runs
Events are handled first in each tick, before animators are stepped and subscriptions recomputed; they may start or stop animators. A part's on(created, ...) runs before any message to it is handled: a world that arrives after dusk sets its lamps on with its start act before it reads its first dusk or dawn, so the two can never race (Loading: start comes before every message). Messages sent while handling are, as recommended, delivered later in the same step in the order sent, with a limit on how many rounds a chain may take; the order between several receivers of one message is not to be relied on.
Who may change what
- Anything in the world can find and change anything else by its path. There is no private and no public in content, and nothing to lock. A handler on the ice may change the skater's speed, and a trade may move a backpack from one visitor to another. Keeping a part's own changes inside that part, with triggers coming in, is good style, not a rule; a part's functions are there for others to call.
- Each rule acts as if it ran alone: it sees one consistent world, and its changes take effect whole. The order in which rules run within the same moment is not to be relied on. The exception is actions: within one moment, actions run in the order the server received them, so of two casts on one being the later one wins.
Where it runs
Where a handler runs follows from what it reads and writes: the riddle's handler reads a server-only field, so it runs on the server. Rules that write fields living on the server or on both run only on the server; reactions that only show (a smile, a wince) may run in each view. To begin with the server runs the world's logic. A rule inside a part with lives: client runs on each visitor's machine, and only there: it is how a sound or a flash is played in each view when the server has sent the message that asks for it (Sounds: a one-shot play).
Bounded running
Each block runs within a budget. Long work yields and resumes, so a runaway loop in one world cannot stall a server shared by everyone in it.
How it connects
- Actions: what a handler may do.
- Subscriptions: the values conditions watch.
- Animators: started by handlers; their ends and markers are triggers.
- Visitors: actions as triggers; speech.
- Physics: areas: what
enteredandleftwatch. - Blocks and control flow: what an acting block may contain.
A second trigger while one is running
A second trigger can be met in different ways: a dusk while the lamps are lit changes nothing; a click during the cap's dip holds the cap down longer; a click during the ride is ignored; a press during a hail is ignored; a spell during a spell replaces it, starting afresh.
Open questions
while: Cone'swhileis a loop inside a block; the "while X, do Y" over time that the design leaves out for now would be a different thing under the same word.- Whether a handler may stop early with
return, as a Cone function may. - Ranging over beings: closed. A condition over beings is written
each v in visitorsaround the rule, Cone's loop, soheldandbecameare per being (Conditions: became and held). The island's glass counts only the first still being it finds, and a second does not restart the count; the manual's rule gives each visitor a count of their own, and the first to stand for 3 s opens it. - Time since a value changed ("at least 1.1 s in this mode") cannot be said with
heldalone; something likesince(mode)would be needed. - State machines cost bookkeeping: one
on(became(mode == X), start(moves.X))per mode. - Collisions beyond waking.
strucktells of a sleeping body woken by a touch; whether a world needs a message for every hard contact between bodies already awake (a pin striking a pin mid-tumble, for a sound), and with what threshold, is open. - Where an event runs: closed. A rule inside a part with
lives: clientruns on each visitor's machine and only there (Where it runs). - Message delivery: bubbling to the parent, timing and order, replies, and who sees a message.