What the riddle is
Three hundred meters under the island lies a labyrinth. Its first great room is the hall of two doors: 28 m wide and 20 m deep, its walls 5.5 m high. In the far wall are two doorways, 16 m apart. The west one leads to the Worm's chamber, a dead end; the east one leads on, down a corridor to the Bull Court. Between the doors stand two guards, each on a low gypsum plinth 1.2 m square, 10.4 m apart: the west guard in a blue kilt, 1.82 m tall, the east guard in red, 1.78 m. Each wears a bronze helmet with a long plume, holds a spear upright and carries a figure-of-eight shield. Each turns to face the nearest visitor within 7 m.
One guard always lies; the other always tells the truth. Which one lies is drawn afresh each time the world starts, and no visitor's browser is ever told. Come within 4 m of a guard and press H: a panel opens offering four questions. Ask one, and the guard answers aloud: the words appear over his head for 6 seconds, readable by anyone within 18 m, with a low note from his head (the blue guard's a little higher). Ask a second time and he says only "You have had your question." Go up out of the labyrinth and come back, and you may ask again. Only one question always leads right: "Which door would your fellow name?" Both guards name the west door, so the other door, the east, is the way.
As a visitor walks the labyrinth, a glowing line, Ariadne's thread, is laid on the floor behind them in a color of their own: a point every meter, cut back when they retrace their steps, taken up again when they come back to it, let go (but left where it lies) when they go up, and cleared when they leave by the exit at the top of the grand staircase.
The listing
field kiltBlue: color = srgb(46, 82, 168) // the west guard's cloth
field kiltRed : color = srgb(176, 44, 36) // the east guard's
// ===== The guard: a design; it knows nothing of lying =====
part guard
field kilt : color
field height: meters
field title : text = "Guard" // what its spoken lines are headed with
field voice : frequency = 98 Hz // the low note its lines are spoken with
part body: guardLook(kilt: kilt, height: height) // kilt, belt, helmet and plume, spear, shield
being(self, targetable: false) // a being that acts: no visitor may take it over, and the zap and the jig skip it
on(created, start(npcbehave.guard(body, post: self.place, watchWithin: 7 m))) // faces the nearest visitor within 7 m, else back to its post's facing
// ===== The labyrinth: values kept for each being =====
part labyrinth
field asked : Table[being, bool] = false, mutable: true, lives: both // one question per visitor
field thread: Table[being, trail] = empty, mutable: true, lives: both // Ariadne's: a line on the floor for each being
field laying: Table[being, ?handle] = None, mutable: true, lives: both // the thread being laid now, if any
part entry, at: (...) // the antechamber's south door: a place that senses
area(self, sphere(radius: 1.2 m))
part underground // the labyrinth's volume: an area part, no drawing
area(self, cylinder(below: -150 m)) // everything below y = -150 m, anywhere on the plan
on(left(underground, by: v), { // going up, by any way
set(asked[v], false); // may ask again on coming back
if let Some(h) = laying[v] { // lets go of the thread; the line stays
stop(h);
set(laying[v], None);
}
})
// Ariadne's thread
fn laid(v: being) animator {
trail(thread[v], following: v.feet, step: 1 m, mergeStraighterThan: 0.995,
retrace: 0.7 m, reelIn: 0.5 m)
}
on(entered(entry, by: v), { // a being with no thread starts one at the entry
if thread[v] == empty {
set(thread[v], [entry.place]);
set(laying[v], Some(start(laid(v))));
}
})
on(became(occupied(underground, by: v) && laying[v] == None // v: each being in turn (no spelling yet)
&& near(v.feet, thread[v], 1.2 m)), {
set(thread[v], cut(thread[v], at: nearest(thread[v], v.feet))); // back onto its own thread
set(laying[v], Some(start(laid(v))));
})
on(message(crossed(exit), by: v), { // left by the staircase door: cleared
if let Some(h) = laying[v] { stop(h); }
set(laying[v], None);
set(thread[v], empty);
})
// ===== The hall of two doors =====
part hallOfTwoDoors // 28 m wide, 20 m deep, the doors in its far wall
part westDoor : doorway, at: (-8, 0, 20.5) // to the Worm's chamber: a dead end
part eastDoor : doorway, at: ( 8, 0, 20.5) // to the corridor: the way on
part westPlinth : plinth(), material: materials.gypsum, at: (-5.2, 0, 17.8) // 1.2 m square, 0.22 m high
part eastPlinth : plinth(), material: materials.gypsum, at: ( 5.2, 0, 17.8)
part westGuard : guard(kilt: kiltBlue, height: 1.82 m, voice: 118 Hz), at: (-5.2, 0.22, 17.8)
part eastGuard : guard(kilt: kiltRed, height: 1.78 m), at: ( 5.2, 0.22, 17.8)
field liar: guard = draw([westGuard, eastGuard], per: run), lives: server // drawn afresh each run; never sent
enum question {
middle, fellow, truth, mine
}
fn words(q: question) text { // what a visitor reads on the button
match q {
case middle { "Which door leads to the middle?" }
case fellow { "Which door would your fellow name?" }
case truth { "Do you always tell the truth?" }
case mine { "Does the door beside you lead there?" }
}
}
fn honest(g: guard) bool { g != liar }
fn other(g: guard) guard { if g == westGuard { eastGuard } else { westGuard } }
fn doorOf(g: guard) doorway { if g == westGuard { westDoor } else { eastDoor } }
fn flip(d: doorway) doorway { if d == westDoor { eastDoor } else { westDoor } }
fn names(g: guard) doorway { if honest(g) { eastDoor } else { westDoor } } // for "middle"
fn answer(g: guard, q: question) text {
let myDoor = doorOf(g);
let fellowSays = if honest(g) { names(other(g)) } else { flip(names(other(g))) };
match q {
case middle { if names(g) == myDoor { "The door beside me." } else { "The door beside my fellow." } }
case fellow { if fellowSays == myDoor { "He would say: the door beside me." }
else { "He would say: the door beside him." } }
case truth { "Yes. I always tell the truth." }
case mine { if honest(g) == (myDoor == eastDoor) { "Yes. My door leads there." }
else { "No. My door does not lead there." } }
}
}
on(action(ask, q, to: g, by: v), { // aimed at a guard, answered by the hall
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);
}
}
})
// ===== The world's rule for every speech act: how this world shows speech =====
on(spoke(speaker, words, to: v), {
var line = speaker.title;
line <- ": ";
line <- words;
show(overlay(box([text(line)], style: styles.hudGlass, padding: (6 px, 2 px)),
above: speaker, by: speaker.height + 0.45 m, pivot: (0.5, 1),
within: 18 m, whenBlocked: fade(real(0.17 s)),
inPlaceOf: namePlate(speaker)),
for: sim(6 s));
play(audiomixer.voice(pitch: speaker.voice), at: speaker, up: 0.9 * speaker.height);
})
Walking through it
The guard: a design that knows nothing of lying
guard is a design: no at:, so it is placed nowhere until copied. Its fields are what differs between guards: kilt color, height, and the title and voice its speech is shown and heard with. Its body is a being, made so by the call being(self, targetable: false) in the body's text: a part that acts each step, that no visitor may take over, and that the zap and the jig skip (a visitor's own being is made with being(self, possessable: true)). Its mind is one call, npcbehave.guard(body, post: self.place, watchWithin: 7 m): the guard turns to the nearest visitor within 7 m, on its own floor, and otherwise back to its post's facing. The guard's look (kilt, belt, helmet and plume, spear, shield) is one vocabulary call. Whether a guard lies is not a property of the guard: it is a fact about the pair, so it lives one level up.
Values kept for each being
The labyrinth holds three tables keyed by being, typed Table[being, T]: one value of a type for each being that comes along, read and written as asked[v]. asked is a bool, false for every being until it asks. thread is a trail, the line a being lays. laying holds the handle of the thread being laid, or None (Values: options). All three live on both the server (lives: both) and every visitor's machine, because every visitor sees a guard refuse a second question and sees everyone's thread (Distribution: values per visitor).
A row needs a lifetime, or the rows of every visitor who ever came would pile up. The lifetimes are stated by relation to the world, as event handlers: going up out of the labyrinth by any way clears asked and lets go of the thread; leaving by the exit clears the thread itself. if let Some(h) = laying[v] unwraps the option before stopping the animator it names (Control flow: patterns); stopping an animator leaves its value where it was, so the line stays where it was laid (Actions: stop).
Ariadne's thread
laid(v) builds an animator without starting it: a trail, which follows a being's feet and lays a line behind it, a point every meter, a nearly straight run merged into one stretch, cut back when the being steps within 0.7 m of an earlier point, shortened when it walks back within 0.5 m of the last stretch. A trail remembers what it laid, as a chase remembers where it got to; it is a seventh kind, not among the six on Animators (open there). Three rules start and end it: entering 1.2 m of the entry with no thread starts one; a being underground, not laying, that comes within 1.2 m of its own line takes it up from there; crossing the exit clears it.
The hall, the doors and the guards
The hall is a part inside the labyrinth, placed in its frame. The doors are copies of the labyrinth kit's doorway; the plinths are shapes; the guards are copies of guard with their own values, each standing 0.22 m up, on its plinth (Designs: a copy states only its differences). The west guard's voice: 118 Hz differs from the design's 98 Hz.
The secret, drawn per run
liar is a field whose type is the design guard, and whose value is one of the two guard parts (Values: parts as values). Its default is draw([westGuard, eastGuard], per: run): one of the two choices, drawn afresh each run, not from the world's seed, which would give the same answer every run to anyone who read the content (Fields: two kinds of randomness). A run lasts from the world's start on the server until the server stops, so it is one draw for everyone: every visitor in the world meets the same liar. It has lives: server: it lives only on the server and is never sent. Nothing writes it after it is drawn, so it is not mutable: true.
The four questions and the answer functions
question is an enum: a closed set of four cases, with their words in a function over it. Because the set is closed, a match on a question must name every case, and the checker says which is missing (Control flow: match). The five helpers and answer are functions inside a part: written on the hall, they see its children and its fields by their bare names, as a method sees its object's. honest reads liar without being handed it. An answer is worked out as references to parts (eastDoor) and turned into words only at the end: the honest guard names the east door as the way to the middle; asked what his fellow would name, the honest guard reports the liar's lie and the liar lies about the honest guard's truth, so both name the west door.
Nothing says where answer runs. It reads liar, which lives only on the server, so it runs on the server (Functions: where a function runs).
The ask handler
ask is the world's own action: a visitor chooses one of the questions, aimed at a guard (Visitors: actions). The browser finds an input for it (a panel with four buttons, say); the content never names a key. The handler receives the value carried (q), the guard addressed (g) and the being that acted (v). It is written on the hall, though the action is aimed at a guard: the guard design has no handler for it, so it passes to the hall, which holds both guards and the liar (Events: who receives an event). It is an acting block (Blocks): a question from farther than 4 m is not heard; one who has asked is told so; otherwise the row is set and the answer said. Cone's if ... else takes exactly one branch.
The speech act, and how the world shows it
say(g, words, to: v) is an act (Actions: say; Text: saying): the guard computes its words as a pure value and performs the act at a moment. to: v names the addressee, the asker, not the audience: the act goes to every visitor's machine, so everyone near sees the plate, and only the asker's panel takes the line. It puts the words nowhere. Only the act crosses the network, from the server to every visitor's machine (Distribution: only acts cross).
The last rule is the world's answer to every speech act, run on each visitor's machine (Overlays: speech). It builds the line, "Guard: The door beside me.", joining text with Cone's <- (Text), and shows it with show(element, for: d) for 6 s of simulation time as an overlay: a small panel of the HUD's dark glass, its bottom middle on a point 0.45 m above the speaker's head, drawn only within 18 m of the eye, fading out over 0.17 s while something stands between the eye and that point, and shown in place of the speaker's name plate (Layouts: placing a layout over a 3-D anchor). It also plays the audio mixer's voice from the speaker's head, 0.9 of its height up, at the speaker's pitch. Another world could show the same act as a subtitle or a line in a chat log, and a visitor's own captions or translation apply on top.