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:

OptionMeaning
within: lo ..= hithe 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: truethe field may change while the world runs; without it the field is fixed
common: trueone value for the design and all its copies; without it every copy has its own
lives: client | server | bothwhere 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:

Where a field lives: client, server, both

Value of lives:MeaningExample
serverLives 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.
bothKept 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.
clientEach 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

How it connects

Open questions

← Parts | Contents | Designs, copies and spawning →