What the lanterns are

Fourteen lanterns light the way to the cottage. Twelve are path lanterns: solar garden lights, 46.2 cm from the ground to the top of the head. Each is a round black stake 3.6 cm across, pushed 22 cm into the ground, with a square head on top: a plinth 14 cm square, four amber panes from 26 to 38 cm up in a bronze frame (corner posts, rails and a round-topped arch on each face), a hipped roof with eaves 19 cm square and a small black solar panel on its top block, and inside a small filament. They stand in six pairs along the last 16 m of the walkway before the cottage's steps: the first pair 1.8 m back from the steps, then a pair every 2.8 m, each lantern 1.5 m out from the walkway's middle, so each pair faces across the walkway. The other two are wall lanterns on the cottage's porch, one each side of the door: the same head at 0.7 of the size, its solar panel bronze like its roof, hung from a bronze bracket whose arm reaches 17 cm out from the wall.

By day the lanterns are dark. When the world's clock reaches dusk (17:28), every lamp fades up over a quarter of an hour of the world's day, which is 15 seconds of scene time, since the world's day lasts 24 minutes; at dawn (06:31) they fade out the same way. How bright a lit bulb looks follows the hour, because the camera's exposure opens up about 5000-fold as the evening darkens: about 2800 nits at dusk, falling to 1.3 nits from 20:00. Each lantern also throws real light: a deep gold pool on the grass and the walkway, reaching 5 m. Walk within 4 m of a lit path lantern and that lantern alone brightens to 1.45 times in 0.4 s; walk away and it dims back. The wall lanterns never brighten: they have no sense of who is near.

The listing

// ===== The world's lamp level: one value every lamp follows =====
field dayLength  : duration = sim(24 min)                   // the world's day: its clock runs 60 times the scene's
field lampLevel  : fraction = 0, within: 0 ..= 1, mutable: true
field lampColor : color = color(1, 0.397, 0.0953)        // about 2700 K: the bulb's glow, the inner light
field glassColor: color = color(1, 0.3, 0.045)           // deeper: the panes' and the bronze's glow

on(created, {                                               // start: a world arriving at night is lit at once
  let h = hour(now(world));
  if h >= sky.duskHour || h < sky.dawnHour { set(lampLevel, 1); } else { set(lampLevel, 0); }
})
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)))

// ===== A bulb's brightness by the hour: world data =====
field bulbBrightness: curve = curve(                        // eight evening hours, log-linear between
    points: [(16.975, 3600 nits), (17.475, 2800 nits), (17.725, 1600 nits), (17.975, 800 nits),
             (18.475, 160 nits),  (18.975, 24 nits),   (19.475, 3 nits),    (19.975, 1.3 nits)],
    between: logarithmic)
fn eveningHour(h: number) number {                          // a morning hour mirrored about noon:
  if h < 12 { 24 - h } else { h }                           // the sun stands as high at 7:00 as at 17:00
}

// ===== The lanterns' materials, held by the world, shared by all fourteen =====
part materials                                              // a part with no frame: a holder
  part lanternBlack: shiny(srgb(12, 13, 17), highlight: 0.9, shininess: 90)        // the stakes
  part lanternMetal: shiny(srgb(88, 62, 40), highlight: 0.22, shininess: 12)       // the wall brackets
  part lanternHead  : [shiny(srgb(88, 62, 40), highlight: 0.22, shininess: 12),
                       litByLayers([world, interior]),                           // frame, roof, plinth
                       glowing(color: glassColor, strength: glow)]
    field glow: nits = 0 nits                      // derived below: not mutable
  part lanternGlass : [shiny(srgb(255, 205, 130), highlight: 0.45, shininess: 30),
                       translucent(covers: 0.12),                                // the panes and lining
                       glowing(color: glassColor, strength: glow)]
    field glow: nits = 0 nits                      // derived below: not mutable
  part lanternBulb  : [plain(srgb(255, 214, 122)),                               // the filament
                       glowing(color: lampColor, strength: glow)]
    field glow: nits = 0 nits                      // derived below: not mutable

derive(materials.lanternBulb.glow,  lampLevel * bulbBrightness.at(eveningHour(hour(now(world)))))
derive(materials.lanternGlass.glow, materials.lanternBulb.glow * 0.09)
derive(materials.lanternHead.glow,  materials.lanternBulb.glow * 0.0015)

// ===== The head both kinds share: a design, placed nowhere =====
part lanternHead
  // its measurements at size 1: fixed and common, read by its shapes by name
  field base       : meters = 0.24 m,  common: true         // the plinth's underside, above the ground
  field glassFoot  : meters = 0.26 m,  common: true         // the glass's foot: the plinth's top
  field glassTop   : meters = 0.38 m,  common: true         // the glass's top: the eaves' underside
  field eave       : meters = 0.095 m, common: true         // the eaves' half width: 19 cm square
  field top        : meters = 0.462 m, common: true         // the head's top: the solar panel's face

  field size       : number = 1, within: 0.5 ..= 1          // own and fixed: feeds construction
  field panel      : ?shape = None                          // own: the mount's solar panel, or none
  field panelIn    : material = materials.lanternHead     // own: what the panel is drawn in
  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: how much brighter when someone is near
  field gain       : number = 1, within: 1 ..= nearGain, mutable: true   // own: how much brighter this one is drawn now
  field cdPerNit   : candelaPerNit = 0.45 candelaPerNit, within: 0 ..= 2, common: true   // one value for the design and its copies

  // its shapes: what varies (size) is passed; the measurements are read
  fn tracery(size: number) shape {                          // the plinth, corner posts, rails and arches
    let arc = fn(k: count) point {
      (0.046 * cos(k * 22.5 deg), 0.326 + 0.046 * sin(k * 22.5 deg), 0.059)
    };
    let sidePieces = List[shape]() <-                        // one side's rails and arch, on the front (+z)
      box(x: -0.052 ..= 0.052, y: 0.292 ..= 0.300, z: 0.056 ..= 0.062),        // the low rail
      box(x: -0.052 ..= 0.052, y: 0.371 ..= glassTop, z: 0.056 ..= 0.062),     // the top rail
      each u in [-0.030, 0.000, 0.030] yield                // three mullions between the small panes
        box(x: u + (-0.002 ..= 0.002), y: glassFoot ..= 0.292, z: 0.056 ..= 0.062),
      box(x: -0.0025 ..= 0.0025, y: 0.300 ..= 0.371, z: 0.056 ..= 0.062),      // the arch's mullion
      bar(from: (-0.046, 0.300, 0.059), to: (-0.046, 0.326, 0.059), width: 0.006, thickness: 0.006),   // its legs
      bar(from: ( 0.046, 0.300, 0.059), to: ( 0.046, 0.326, 0.059), width: 0.006, thickness: 0.006),
      each k in 0 ..< 8 yield                              // its half circle
        bar(from: arc(k), to: arc(k + 1), width: 0.006, thickness: 0.006);
    let side = join(sidePieces);
    let pieces = List[shape]() <-
      box(x: -0.07 ..= 0.07, y: base ..= glassFoot, z: -0.07 ..= 0.07),        // the plinth, 14 cm square
      each (sx, sz) in [(1, 1), (-1, 1), (1, -1), (-1, -1)] yield              // four corner posts
        box(x: sx * (0.052 ..= 0.062), y: glassFoot ..= glassTop, z: sz * (0.052 ..= 0.062)),
      each f in 0 ..< 4 yield turn(side, by: f * 90 deg, about: up);        // the side, four ways round
    scale(join(pieces), by: size)
  }

  fn panes(size: number) shape {                            // four amber panes, a pressed floor and a ceiling
    let pane = face(points: [(-0.054, glassFoot + 2 mm, 0.058), (0.054, glassFoot + 2 mm, 0.058),
                             (0.054, glassTop - 2 mm, 0.058), (-0.054, glassTop - 2 mm, 0.058)]);   // facing front
    let tip = (0.0125, 0.004, 0.0125);                      // one cell of the floor: 2.5 cm square, 4 mm high
    let corners = [(0, 0, 0.025), (0.025, 0, 0.025), (0.025, 0, 0), (0, 0, 0)];
    let pyramid = join(List[shape]() <-
      each k in 0 ..< 4 yield face(points: [corners[k], corners[(k + 1) % 4], tip]));   // four triangles
    let pieces = List[shape]() <-
      each f in 0 ..< 4 yield turn(pane, by: f * 90 deg, about: up),
      each n in 0 ..< 16 yield                             // the floor: 4 by 4 pyramids, 1 mm over the plinth
        move(pyramid, by: (-0.05 + 0.025 * floor(n / 4), glassFoot + 1 mm, -0.05 + 0.025 * (n % 4))),
      face(points: [(-0.05, glassTop - 1 mm, -0.05), (0.05, glassTop - 1 mm, -0.05),
                    (0.05, glassTop - 1 mm, 0.05), (-0.05, glassTop - 1 mm, 0.05)]);   // the ceiling, facing down
    scale(join(pieces), by: size)
  }

  fn filament(size: number) shape {                         // two uprights, a bar across, a small hot coil
    let pieces = join([
      box(x: -0.0075 ..= -0.0045, y: 0.30 ..= 0.37, z: -0.0015 ..= 0.0015),
      box(x:  0.0045 ..=  0.0075, y: 0.30 ..= 0.37, z: -0.0015 ..= 0.0015),
      box(x: -0.0075 ..=  0.0075, y: 0.366 ..= 0.37, z: -0.0015 ..= 0.0015),
      box(x: -0.0045 ..=  0.0045, y: 0.32 ..= 0.35, z: -0.0045 ..= 0.0045)]);
    scale(pieces, by: size)
  }

  fn roof(size: number) shape {                             // a fascia, a hipped roof in two pitches, a top block
    let eaves = glassTop + 8 mm;                            // the fascia's top
    let hipped = loftAlong(profiles: [outline.rectangle(width: 2 * eave, height: 2 * eave),
                                      outline.rectangle(width: 0.14,  height: 0.14),
                                      outline.rectangle(width: 0.104, height: 0.104)],
                           spine: spine.line(from: (0, eaves, 0), to: (0, eaves + 5 cm, 0)),
                           stations: [0, 0.28, 1],           // shallow at the eaves, steeper to the block
                           caps: false);                     // open: it sits on the fascia
    let pieces = join([
      box(x: -eave ..= eave, y: glassTop ..= eaves, z: -eave ..= eave),              // the fascia
      hipped,
      face(points: [(-0.052, eaves + 5 cm, 0.052), (0.052, eaves + 5 cm, 0.052),
                    (0.052, eaves + 5 cm, -0.052), (-0.052, eaves + 5 cm, -0.052)]),  // the roof's top, facing up
      box(x: -0.05 ..= 0.05, y: eaves + 5 cm ..= eaves + 7 cm, z: -0.05 ..= 0.05)]);   // the top block
    scale(pieces, by: size)
  }

  fn solarPlates(gap: meters) shape {                       // a panel on the top block, 4 mm thick, 8.4 cm square
    let y = top - 4 mm ..= top;
    if gap > 0 m {                                          // two plates: the bronze block shows between
      join([box(x: -0.042 ..= 0.042, y: y, z: -0.042 ..= -gap / 2),
            box(x: -0.042 ..= 0.042, y: y, z: gap / 2 ..= 0.042)])
    } else {
      box(x: -0.042 ..= 0.042, y: y, z: -0.042 ..= 0.042)   // one plate
    }
  }

  part glass: panes(size),    material: materials.lanternGlass, at: hangsAt
  part bulb : filament(size), material: materials.lanternBulb,  at: hangsAt, castsShadow: false
  part cap  : roof(size),     material: materials.lanternHead,  at: hangsAt
  part plate: tracery(size),  material: materials.lanternHead,  at: hangsAt, castsShadow: false
  if let Some(p) = panel {
    part solar: scale(p, by: size), material: panelIn, at: hangsAt
  }
  part light: pointLight(color: color(1, 0.14, 0.01), source: 2 cm, reach: 5 m, castsShadow: true),
              at: hangsAt + (0, 0.335 m * size, 0)
  part inner: pointLight(color: lampColor, source: 2 cm, reach: 0.35 m, layers: [interior]),
              at: hangsAt + (0, 0.335 m * size, 0)

  derive(glass.tint, gain)                                  // this lantern's two parts, not the materials
  derive(bulb.tint,  gain)
  derive(light.intensity, materials.lanternBulb.glow * cdPerNit * gain)
  derive(inner.intensity, materials.lanternBulb.glow * 0.006 candelaPerNit * gain)

// ===== The path lantern: the head on a stake, with nearness =====
part pathLantern: lanternHead(panel: Some(solarPlates(gap: 2 mm)),         // two plates with a 2 mm gap,
                              panelIn: materials.lanternBlack)           // in the stake's black
  field near: bool = false, lives: client                   // each visitor's own; derived, so not mutable
  fn stake() shape {                                        // a round stake 3.6 cm across, 22 cm underground
    lathe(profile: [(0, -0.22), (0.008, -0.20), (0.018, -0.12), (0.018, base + 4 mm), (0, base + 4 mm)],
          steps: 14)
  }
  part post: stake(), material: materials.lanternBlack
    area(self, sphere(radius: 4 m, at: (0, 0.2, 0)))
  derive(near, occupied(post, by: me))
  on(created, start(chase(gain, toward: if near && lampLevel > 0 { nearGain } else { 1 },
                          speed: rate(nearGain - 1, per: sim(0.4 s)))))

// ===== The twelve: six pairs along the walkway's last 16 m =====
part pathLanterns                                          // a frameless holder: hide it, hide all twelve
  each p in 0 ..< 6 {
    each (k, side) in [(0, -1), (1, 1)] {                  // left, then right, of the walkway
      let way = land.walkway;
      let spot = way.spotAt(way.fromEnd(1.8 m + 2.8 m * p), across: side * 1.5 m);   // a point on the ground
      part lantern[2 * p + k]: pathLantern, at: spot
    }
  }

// ===== The wall lantern: a new design from the old =====
part wallLantern: lanternHead(size: 0.7, panel: Some(solarPlates(gap: 0 m)),          // one plate, in the head's bronze
                              hangsAt: (0, -(top * 0.7) - 4 mm, 0.17),               // 0.327 m under the bracket's top
                              cdPerNit: 1.2 candelaPerNit)
  fn wallBracket() shape {                                     // a plate on the wall and an arm 17 cm out
    join([box(x: -0.04 ..= 0.04,   y: -0.07 ..= 0.07,   z: 0 ..= 0.015),
          box(x: -0.015 ..= 0.015, y: -0.015 ..= 0.015, z: 0.015 ..= 0.17)])
  }
  part bracket: wallBracket(), material: materials.lanternMetal   // full size: the bracket's own frame

The two wall lanterns hang on the cottage's porch, which the cottage's own text declares. They are written in a file of their own, each line declaring its lantern by a path through the cottage to the porch:

part cottage.porch.wallLantern1: wallLantern, at: (-0.7, 2.4, 0)
part cottage.porch.wallLantern2: wallLantern, at: (0.85, 2.4, 0)

Walking through it

The world's lamp level

lampLevel is a field of the world, the root part (Parts): written outside any part, in a file that sits in the world's top folder, so that what it declares is the world's own (Parts: a file that adds to a part). It is a fraction, ranged 0 to 1 by within: 0 ..= 1, starting at 0, and mutable: true because rules change it. It belongs to the world and not to the lanterns because more than lanterns follow it: the five lit windows of the cottage glow from the same level. lampColor and glassColor are fixed fields: named colors the glows and the inner light use (Values: colors). dayLength is a world setting: a day of world time lasts 24 minutes of the scene's, so the world clock runs 60 times as fast (Values: the day length).

Two events and one start act move it:

How bright a bulb is at an hour

The brightness is world data, not code. bulbBrightness is a fixed field of the world holding a curve: the eight evening hours and the bulb's radiance at each (3600 nits half an hour before dusk, 2800 at dusk, down to 1.3 at 19:58), blended on a log scale between them (between: logarithmic), so each step of the hour divides the brightness by the same factor, as the exposure multiplies. The curve holds at its ends: 3600 nits before the first hour, 1.3 after the last. Its points carry their unit, so bulbBrightness.at(h) is in nits. A morning hour is read as the evening hour mirrored about noon, where the sun stands at the same height: eveningHour, a two-line function, turns 7:00 into 17:00 and leaves an afternoon hour as it is. Its parameter is h, not hour, because a local may not hide a name its own block reads, and the block's caller reads the vocabulary's hour (Blocks: local names).

The materials and their glow

The five lantern materials are held by a frameless part at the world's root, materials, by convention, and named by path from every lantern (materials.lanternGlass). Each is a part holding a value of type material, declared part lanternGlass: [shiny(...), ...] (a list: a base material, then its modifiers), with an identity: every bulb, path or wall, is drawn in the one lanternBulb, so one change to its glow reaches all fourteen. Each glowing material says its glow's color and where its strength comes from with one modifier, glowing(color: ..., strength: glow): the bulb in the lamp's color, the glass and the bronze head in the deeper glassColor, each at the strength its own glow field holds (Materials: a glow). The bronze is two materials of one color only because the head's is lit on the interior light's layer too (litByLayers([world, interior])) and the bracket's, naming no layers, is on the built-in world layer alone, as the stakes and the ground are (Lights: light layers).

The three derive lines are subscriptions: the bulb glows at the lamp level times the hour's brightness; the glass at 0.09 of the bulb, so the interior reads amber; the bronze at 0.0015, "a faint warm lift, not a light". They subscribe to the clock through now(world): between 18:00 and 20:00 the bulbs dim from about 800 to 1.3 nits with no animator running, only a formula.

The head: a design with own and common fields

lanternHead has no at, so it is a design, a part placed nowhere. It is a design of its own because both kinds of lantern share it and differ in their mounts. Its fields:

The shapes are construction pipelines, each a function whose result is a shape, fn name(...) shape { ... }, written inside the head, a function of the head's size that reads the head's measurements by name, and called by the parts that draw them; every name in them is a tool from the Appendix: shape tools or one of the head's fields. tracery is the plinth, the corner posts and one side's rails, mullions and arch, made of boxes and bars, the arch's eight bars from a closure (Functions: closures); a mullion is 4 mm wide about its place, u + (-0.002 ..= 0.002), a range shifted by a number (Values: ranges); the side is turned four ways round with turn, and the whole scaled by size. panes is four flat faces, a floor pressed into 4 by 4 small pyramids (each four triangles, face of three points, moved into place) and a ceiling facing down; filament four small boxes; roof a fascia, a hipped roof lofted through three squares, open at both ends (caps: false), since the fascia closes it below (Sculpting: the roof by loftAlong), a flat face over its top, 2 mm wider each way than the block, and the top block. Each shape's pieces are gathered into a list, written out (join([a, b, c])) or built with Cone's collection form (List[shape]() <- box(...), each f in 0 ..< 4 yield turn(...)), and join(pieces) gives the joined shape; the shape's last line, scale(join(pieces), by: size) or scale(pieces, by: size), is its value (Blocks: joining shapes). The floor of pyramids is one loop over sixteen cells, the row floor(n / 4) and the column n % 4. Points mix plain numbers and lengths, (0.054, glassTop - 2 mm, 0.058): a plain number in a shape is meters (Values: plain numbers in shapes).

The head's panel is a shape, the solar panel on the top block, or None for none, and panelIn the material it is drawn in: each mount passes its own. The path lantern's is two plates with a 2 mm gap, through which the bronze block shows as one pale line, in the stakes' black; the wall lantern's is one plate in the head's bronze. Both are calls of the head's solarPlates, written in the copy's arguments, which are worked out inside the copy (Designs: a copy's values). The head draws whichever it is given at its own size, scale(p, by: size). A panel is a part of its own because a shape has one material. Each drawn part names one shape after the colon and one material with the material: option, and its place with at:. The bulb and the plate cast no shadow (castsShadow: false, an option of a drawn part): the bulb is the light, and the tracery's shadow thrown by a point light a few centimeters away was a hard sunburst on the ground.

The two lights are parts like any other: light, at the filament's middle (0.335 m up at size 1), a deep gold, a source 2 cm in radius, reaching 5 m and casting shadows (castsShadow: true, an argument of the light, Lights: shadows), on the built-in world layer since it names no other; inner, at the same point in the lamp's own color, reaching 35 cm on the interior layer, which only the head's bronze answers, so the frame reads lit from within without brightening the grass a meter away. Their strength is a subscription: the bulb's glow, in nits, times candela per nit, times the gain. Three effects (the evening's schedule, the hour, nearness) meet in one formula the author wrote (Subscriptions: several effects on one value). The tints multiply this lantern's glass and bulb by the gain, leaving the shared materials untouched; a tint given a number is the gray of that number, so the gain brightens every color alike (Materials: the tint).

The path lantern: a copy that adds

pathLantern: lanternHead(panel: ..., panelIn: ...) is a copy that gives its head a two-plate black panel and adds three things (Designs: a new design from an old): the stake (stake(), a function of the path lantern's own that makes a shape, one lathe call over a five-point outline in 14 steps, its top 4 mm over the head's base, Sculpting) with the space round it, a field near, and two rules. area(self, sphere(radius: 4 m, at: (0, 0.2, 0))), a call in the post's body, is a sensing area, registered once with the post: a ball of 4 m round a point 0.2 m up it, in the post's frame, so it moves with the lantern, written with the same sphere a shape would use (Physics: areas). occupied(post, by: me) turns the engine's coming-in and going-out into one value a formula can read, true while this machine's visitor is inside; so near, computed for each visitor, is lives: client, and each visitor sees the lantern by them brighten. The chase is the nearness: started when each copy is made and never stopped, it is "a value always following another" (Animators: two kinds of chase). It moves at a steady rate, rate(nearGain - 1, per: sim(0.4 s)) (0.45 per 0.4 s of simulation time), towards nearGain (1.45) while that visitor is near and the lamps are lit at all, else towards 1; a visitor who steps in for 0.2 s takes the gain only to about 1.22 before it turns back. toward: is a value block: the engine reads it again each tick and moves the chase's goal.

Because the rule is written on a design, it runs once for each copy: twelve chases of twelve gains (Designs: rules on a design run per copy).

The twelve: a loop that places copies

pathLanterns is a frameless holder, so its children are placed in the world's frame, and hiding it hides all twelve (Generators: a counted loop). The loop is Cone's each, twice: six pairs, and in each pair a side. land.walkway is found by name from the world's root, where land is (Parts: how a name is found). way.spotAt(way.fromEnd(...), across: side * 1.5 m) asks the walkway, a spine the land lays on its surface (Terrain), for the point a distance back along it from its end, 1.5 m out to its left (side -1) or its right (1), at right angles to its direction there (Sculpting: what a spine answers). The spot is a point (x, y, z) on the ground, so the lantern is placed at: spot and stands on the land; the walkway's spots read the land's height, so the land is built before the lanterns, though they are not inside it (Loading: order follows dependencies). A part declared in a loop is one of a numbered family, named with an index as a list's element is: lantern[2 * p + k] is read as lantern, and one of them as lantern[0], pair 0's on the left, nearest the steps, or lantern[11], pair 5's on the right, 15.8 m out. A copy states nothing but its place, so every other change to the design reaches all twelve. Its parts are pathLanterns.lantern[11].glass and so on (Parts: paths).

The wall lantern: a copy that changes

wallLantern: lanternHead(...) gives the head's own fields new values (size 0.7; a panel of one plate, drawn in the head's bronze, panelIn's default; the head hung 0.17 m out and below the bracket's top by the head's own height at 0.7 and 4 mm more, -(top * 0.7) - 4 mm, 0.327 m) and adds the bracket, a shape function of its own, drawn full size beside the head built at 0.7 (Construction: two kinds of scale). It has no area and no chase, so its gain stays 1. It also gives the common cdPerNit a value of its own, 1.2: the wall lanterns throw a stronger light from under the porch roof. A copy that sets a common field starts a family of its own: 1.2 is the one value of wallLantern and its two copies, and the path lanterns keep their 0.45 (Designs: a copy that sets a common field). The two copies hang on the cottage's porch, in the porch's frame. They are written in a file of their own whose lines declare them by a path, part cottage.porch.wallLantern1: wallLantern, at: (-0.7, 2.4, 0), under the porch, which the cottage's text declares once (Parts: a file that adds to a part).

Where it all runs

Only one line of the listing says which machine works something out (Distribution). The level, the glows and the light strengths are formulas of the clock and one start moment, so every machine works them out for itself. The gain is a chase, a value that remembers where it has got to, driven by near, and near is client: each visitor's machine works out its own visitor's nearness, its own chases and its own tints, and sends nothing. Had the lantern read occupied(post) instead, true while anyone is near, everyone would see a lantern brighten whoever stood by it (Lights: whose nearness).

← Appendix: shape tools | Contents | The balloon ride →