A field is a named value a part holds, as a field is in a struct: a lantern's size, its gain, the world's lamp level, which guard lies. Every field has a type, and usually a unit, a range and a default. Beyond that, three questions are asked of every field, and the declaration answers each: does it change while the world runs (fixed, or mutable: true)? Does each copy of a design have its own value, or is there one for them all (own, or common: true)? And where does it live: on each visitor's machine, on the server, or on both (lives: client, lives: server, lives: both)?
How you write it
field name: type = default, within: lo ..= hi, mutable: true, common: true, lives: client | server | both
The type is always a type, never a bare range: a unit name (meters, candelaPerNit; Values: a unit as a type), a duration, a kind of value (number, fraction, bool, moment, color, place, shape, material), a design's name (guard), a record, an enum, a List[T], or a Table[being, T] keyed by being. After the default come the options, named by the argument rule (Expressions), in this order, each written only when it applies:
| Option | Meaning |
|---|---|
within: lo ..= hi | the range of values the field may hold, in the type's unit (a rule, an editor's slider and a hint to the encoding) |
mutable: true | the field may change while the world runs; without it the field is fixed |
common: true | one value for the design and all its copies; without it every copy has its own |
lives: client | server | both | where the field is kept |
part lanternHead // a design (designs.html) field size : number = 1, within: 0.5 ..= 1 // own and fixed: 1 for the path lanterns, 0.7 on the wall field panel : ?shape = None // own: the mount's solar panel, or none field hangsAt : place = (0, 0, 0) // own: where the head sits on its mount field nearGain : number = 1.45, within: 1 ..= 2, common: true // fixed and common: a named number field gain : number = 1, within: 1 ..= nearGain, mutable: true // own and changing: brighter while someone is near field cdPerNit : candelaPerNit = 0.45 candelaPerNit, within: 0 ..= 2, common: true // one value for the design and its copies field lampLevel: fraction = 0, within: 0 ..= 1, mutable: true // the world's: one lamp level for every lamp part labyrinth.hallOfTwoDoors field liar : guard = draw([westGuard, eastGuard], per: run), lives: server // drawn each run; never sent part labyrinth field asked: Table[being, bool] = false, mutable: true, lives: both // one question per visitor
Defaults and construction
A copy gives its fields' values by the argument rule: a field without a default is required and may be given by position, in declared order, or by name; a field with a default must be named. Positional values come first, and after the first named value every value is named. A field without a default is declared before those with defaults. A copy gives its values by name: lanternHead(size: 0.7, panel: Some(solarPlates(gap: 0 m))); a design with required fields may take them by position, as guard(kiltBlue, 1.82 m) would if kilt and height were its two required fields.
Fixed and mutable
A field without mutable: true is fixed: given when the part is made and never changed while the world runs. A field with it may be changed by an event's set or by an animator. The declaration could in principle be inferred from whether anything writes the field; it is kept because it has consequences: an immutable field that feeds construction can never force a rebuild, and the engine knows it need never send it again.
Named numbers: a fixed field stands for its value
A fixed field (one without mutable: true, and not defined by a derive) never changes, so it may be used anywhere a literal may: in a range's bounds, in a default, in a chase's goal and speed, in a duration. A number used in several places is written once, given a name, and changed in one place. The lantern's nearness gain is used three times, as the gain's top, as the chase's goal and in its speed:
field nearGain: number = 1.45, within: 1 ..= 2, common: true // fixed: how much brighter a lantern is drawn near it
field gain : number = 1, within: 1 ..= nearGain, mutable: true // a range bound
on(created, start(chase(gain, toward: if near && lampLevel > 0 { nearGain } else { 1 }, // a goal
speed: rate(nearGain - 1, per: sim(0.4 s))))) // a speed
A fixed field's default may itself be an expression of fixed values: field wallTop: meters = floorTop + 1.1 m, field laneHead: place = rack - lane * laneLength. It is worked out once, when the part is built, in the order what reads what (Loading: order follows dependencies).
A design's measurements that every one of its shapes shares (a plinth's height, the glass's foot) are named numbers the same way: fixed common fields, which the shapes written in the design read by name, without their being passed (Construction: a shape is a function).
part lanternHead field glassFoot: meters = 0.26 m, common: true // read by panes, tracery and roof field glassTop : meters = 0.38 m, common: true
Own and common
Without common: true, a field is own: every copy of a design has its own value (each lantern's size, its frame, its gain). With common: true, there is one value, held by the design and read by every copy, and it moves with the design. Change cdPerNit on the path lanterns and all twelve change together (designs.html). Three consequences:
- A rule written on a design runs for each copy; a rule that reads and writes only common fields runs once.
- Construction that reads only common fields, or own fields whose values are equal, is built once and drawn many times.
- A copy that sets a common field starts a family of its own:
lanternHead(cdPerNit: 1.2 candelaPerNit)gives the new design its own common value, shared by it and its own copies;lanternHeadand its other copies keep theirs. The wall lanterns' light is 1.2 and the path lanterns' stays 0.45 (Designs).
Where a field lives: client, server, both
Value of lives: | Meaning | Example |
|---|---|---|
server | Lives only on the server and is never sent. This is what keeps a per-run secret out of a description anyone can download. Any logic that reads it can run only on the server. | Which guard lies: nothing in the protocol carries it; a browser is told only the line a guard said. |
both | Kept by the server and seen by every visitor. | Whether a visitor has had its question: every visitor can see that a guard refuses a second one. |
client | Each visitor's machine keeps its own; two visitors may see different values, and that is correct. | A face's smile, stepped in each view; a paint splat's fade. A field computed for each visitor carries lives: client: a path lantern's near, true on a visitor's machine while that visitor is within 4 m (occupied(post, by: me), Physics: areas), so each visitor sees the lantern by them brighten. |
Where a field lives mostly follows from where the rules that read and write it run: if the server runs combat, the server's rules decide which fields change and what the protocol sends. The label is kept anyway, partly to keep the protocol minimal. The option is written only when it matters; a field that says nothing lives where its rules put it.
Fields keyed by being
Some values exist once per visitor. A field whose type is Table[being, T] holds one value of T for each being that comes along, read and written as asked[v]. Content still never names a player: the key is a handle on a being, whatever body a visitor drives (visitors.html). A keyed value needs a lifetime, and the lifetime is an event, stated by relation to the world:
on(left(underground, by: v), set(asked[v], false)) // a row lasts while its being is below
Keys may be compound: an illustrated faction standing is field standing: Table[(being, faction), number] = 0, within: -1 ..= 1, mutable: true, lives: server, written only by world rules.
Derived fields
A field may be declared and then defined by a subscription: field near: bool = false, lives: client with derive(near, occupied(post, by: me)). A derived field is never written by set or by an animator; to change it, change one of its inputs (subscriptions.html). A derived field is not marked mutable. mutable: true says that an event's set or an animator writes the field; derive writes a derived field, and nothing else may, so the mark would say something false. A field a chase or a transition moves (gain, lampLevel) is written by an animator and is mutable: true.
World settings are fixed fields of the root
The world is the root part (parts.html), so its settings are its fixed fields, and their types are the contracts of rules the browser always runs: the day clock, and where a being that is lost is set down (field recoverAt: place, Physics: the ragdoll). The walking, falling and landing numbers are not among them: they are the engine's own settings (Physics). Written as fields without mutable: true, they need no new word. The day length is one: field dayLength: duration = sim(24 min) says how fast the world clock runs against the scene's (Values: the day length). Others are the sky's: field latitude: angle = 45 deg and field date: count = 288 (the day of the year), which the sky package reads by those names, and the moon's lit share moonLit (Sky and weather). A setting must be either given or derived, never both: dawn's hour follows from the date and the latitude, so a world that gives those does not also give dawn; it asks the sky, sky.dawnHour (Sky and weather).
Two kinds of randomness
A field's default may come from the world's seed, the same on every machine and every run (the rocks of a scatter, say), or be drawn afresh each run with draw(choices, per: run), so a puzzle's answer cannot be read from the description (the riddle's liar). A run lasts from the world's start on the server until the server stops, so draw(choices, per: run) is one draw for everyone in the world. A test that needs to fix the draw (so the east guard lies) does so with the browser's test tooling, not with anything in the content (Outside the content: fixing a draw for a test).
What it means
- A field is one value. There is no hidden stored value under a shown one: an animated field holds whatever its last motion left, and formulas read it as spreadsheet cells read a cell.
- A field's value at execution is part of the part's execution state, beside its id, its current placement and its products. That state is plain values: what the server stores, sends to a visitor who arrives late, and saves.
- A field is addressable. Any value the author declares can be named and read by the rule systems allowed to see it. Which parts may write which fields is open (events.html).
- Metadata is for the tools too. Units, ranges and defaults are carried for the editors people build worlds with, not only for checking.
How it connects
- Values and types: the types a field may have.
- Designs: own and common across copies.
- Subscriptions, events and animators: the three ways a field changes.
- Distribution: what client, server and both mean on the network.
- Visitors: beings, the keys of per-visitor fields.
Open questions
- Whether
mutableshould be inferred from whether anything writes the field. - A field shown only to its own visitor: a quest log needs a value the server keeps and only that visitor sees. A fourth place beside client, server and both ("shown to its being"), a server field told by private reply, or
both. - Relevance to each view: "only for faces within 40 m of the eye" is neither client, server nor both.
- Types used but not defined:
region,set of being,moment, enums, tables keyed by being. - Field types. The balloon's ride is a field of type
animator, because two rules need the same built animator; it answerslength()andtimeAt(along:)(Animators: asking an animator). The burner's schedule is made ofspans, bypulseanduntil(Blocks). A part'soffsethas the typeoffset(Frames). - Lifetimes of keyed values: written as handlers; whether a keyed field can declare its own lifetime.
- Persistence: which fields survive a server restart.