DelveTalkcome in

Inspecting kind/planchette · the kinds · the language · the modules

{name}

Go there its page the town

id
kind/planchette
called
{name}
kind chain
kind/planchette → kind/inhabitant → record
owner
the town
made by
the town
version
6
pin
hagap-fopan
state
13706 bytes

Its spells

  • makedelvetalk kind/planchette make / name: <text, 1 to 64 characters> / haunts: <text, 1 to 128 characters> / tide: <text, 1 to 128 characters>
    its Bend

To cast them, come in.

Who may do what

What its law admits now, before the spell's own Bend has its say.

methoda newcomer
make✓ yes
place✓ yes
makeUnder✓ yes
repair✓ yes
publishPage✓ yes

Its law

  • host opener the hand that opened the world may amend any lawrequest.kind == 2 and request.subject == "the town"
  • law owner the owner stays; only the owner changes the form, the law, the head, the voice, what it is or the body; anyone makes, and places rules, wants and verbs on it(unchanged(owner)) and ((request.subject == new.owner) or ((((((((request.kind == 0) or (request.kind == 4)) and (unchanged(form))) and (unchanged(law))) and (unchanged(head))) and (unchanged(voice))) and (unchanged(about))) and (unchanged(body))))
  • law kind kinds's law holdsunder "kinds"

Its Bend

what it runs: its entry module 20 lines
layer over ./Kind.obend
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Form.obend as Form
import ./Rows.obend as Rows
type State = Super.State
form make:
  name: text 1..64
  haunts: text 1..128
  tide: text 1..128
def make(state: State, input: MakeInput, context: Abi.Context) -> Activity<Super.Done>:
  Super.making(state, Lists.List.cons({head: {name: "name", value: input.name}, tail: Lists.List.cons({head: {name: "haunts", value: input.haunts}, tail: Lists.List.cons({head: {name: "tide", value: input.tide}, tail: Lists.List.nil()})})}), "", context)
form place:
  name: text 1..64
  haunts: text 1..128
  tide: text 1..128
  id: text 1..64
def place(state: State, input: PlaceInput, context: Abi.Context) -> Activity<Super.Done>:
  Super.making(state, Lists.List.cons({head: {name: "name", value: input.name}, tail: Lists.List.cons({head: {name: "haunts", value: input.haunts}, tail: Lists.List.cons({head: {name: "tide", value: input.tide}, tail: Lists.List.cons({head: {name: "id", value: input.id}, tail: Lists.List.nil()})})})}), "", context)
the body this kind lays on what it makes 169 lines

Each thing of this kind runs this layer over what the kind is of.

layer over ./Record.obend
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Document.obend as Document
import ./Form.obend as Form
import ./Plan.obend as Plans
import ./World.obend as World
import ./Card.obend as Card
import ./Scenes.obend as Scenes
import ./Relation.obend as Relations
import ./Rows.obend as Rows
import ./Text.obend as Text
# The planchette's body, over the inhabitant's (`kind/planchette`; yla.delve.town's séance of 2026-10-13): a shared,
# slow spelling. A hand at the table (one in its place, the parlour) nudges it toward one letter, a number, YES, NO or
# GOOD BYE: a line of theirs, `nudge`. A hand's leaning is its newest nudge since the planchette last moved. When two
# hands lean toward the same letter it raises `agreed`, and its kind's rule `agreed` casts its own `move`, which moves
# it there: its own line. The same hand twice running is refused NOT YOUR TURN. At the tide's turn (every sixth tick),
# by its own rule on the tide, while any hand leans it draws one leaning from the turn's own entry (Scenes.turnDraw,
# which replay reproduces) and moves there: it moves on its own clock. GOOD BYE is a place it moves away from: agreed
# or drawn, its line is THEY ALL SAY THAT. A hand lifts (`lift`); after the last hand lifts, at the tide's next tick by
# its second rule, it moves one letter more by itself, W. Its own lines are what it has spelled.
type State = Super.State
form nudge:
  toward: text 1..12
form lift:
form move:
form drift:
# Its rules on the tide, as itself: the tide's turn while a hand leans, and the tick after the last hand lifts.
def haunting(state: State, context: Abi.Context) -> Lists.List<Super.Haunting>:
  Lists.append(Lists.append(Lists.List.nil(), {on: Super.tide(state), name: "{Text.identifier(context.object)}_turns", when: "request.method == \"tick\" and new.ticks % {natText(turnTicks())} == 0 and \"astir\" in \"{context.object}\".vars.name", then: "delvetalk {context.object} drift"}), {on: Super.tide(state), name: "{Text.identifier(context.object)}_after", when: "request.method == \"tick\" and \"lifted\" in \"{context.object}\".vars.name", then: "delvetalk {context.object} drift"})
# The tide's turn: every sixth hourly tick.
def turnTicks() -> Nat:
  6n
def verbs() -> String:
  "nudge lift"
def moves() -> String:
  "haunt move drift"
# The board, yla's: YES · NO, A to Z, 1 to 0, GOOD BYE.
def board() -> String:
  "YES NO A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 9 0"
def goodbye() -> String:
  "GOOD BYE"
# What a nudge names on the board, as the board writes it ("" for nothing on it), in any case.
def onBoard(toward: String) -> String:
  let said = Text.lower(Text.trim(toward))
  if said == "good bye" || said == "goodbye" then goodbye() else named(said)
def named(said: String) -> String:
  match Lists.find(Text.words(board()), fn(w: String) -> Bool: Text.lower(w) == said):
    case some(w): w.value
    case none(_): ""
# What it says when it moves to GOOD BYE: it moves away from it, like it's being polite about it.
def theyAll() -> String:
  "THEY ALL SAY THAT"
# The verb of each move of its own: two hands agreeing, its own clock, away from GOOD BYE, the letter after the hands.
def isMove(line: Rows.Line, id: String) -> Bool:
  line.author == "@{id}" && (line.verb == "moved" || line.verb == "drifted" || line.verb == "away" || line.verb == "last")
def newestFirst(state: State) -> Lists.List<Rows.Line>:
  Lists.reverse(Relations.order(state.lines, fn(l: Rows.Line) -> Nat: l.n))
# Its lines since it last moved, newest first, and the nudges among them.
def since(lines: Lists.List<Rows.Line>, id: String) -> Lists.List<Rows.Line>:
  match lines:
    case nil(_): lines
    case cons(c): if isMove(c.head, id) then Lists.List.nil() else Lists.List.cons({head: c.head, tail: since(c.tail, id)})
def nudges(state: State, id: String) -> Lists.List<Rows.Line>:
  Lists.filter(since(newestFirst(state), id), fn(l: Rows.Line) -> Bool: l.verb == "nudge")
# Each hand's newest of the lines given, newest first.
def newestEach(lines: Lists.List<Rows.Line>) -> Lists.List<Rows.Line>:
  match lines:
    case nil(_): lines
    case cons(c): Lists.List.cons({head: c.head, tail: newestEach(Lists.filter(c.tail, fn(l: Rows.Line) -> Bool: l.author != c.head.author))})
# The hands leaning, each toward its newest nudge since it moved.
def leanings(state: State, id: String) -> Lists.List<Rows.Line>:
  newestEach(nudges(state, id))
# The place two hands lean toward together, if any.
def agreedOn(leaning: Lists.List<Rows.Line>) -> Lists.Maybe<Rows.Line>:
  Lists.find(leaning, fn(l: Rows.Line) -> Bool: Lists.length(Lists.filter(leaning, fn(m: Rows.Line) -> Bool: m.text == l.text)) >= 2n)
# The hands on it: each whose newest nudge or lift is a nudge.
def hands(state: State) -> Lists.List<Rows.Line>:
  Lists.filter(newestEach(Lists.filter(newestFirst(state), fn(l: Rows.Line) -> Bool: l.verb == "nudge" || l.verb == "lift")), fn(l: Rows.Line) -> Bool: l.verb == "nudge")
def flagged(state: State, name: String) -> Bool:
  Lists.any(state.vars, fn(v: Scenes.Var) -> Bool: v.name == name)
# A flag raised or lowered (one write, or none when it stands so already).
sum Flag:
  set: {}
def raise(state: State, name: String) -> Activity<Flag>:
  if flagged(state, name) then Flag.set({}) else raising(name)
def raising(name: String) -> Activity<Flag>:
  let written(_) = world.write(extend(keep(), {vars: Plans.Entries.append({item: {name: name, value: ""}})}))
  Flag.set({})
def drop(state: State, name: String) -> Activity<Flag>:
  if flagged(state, name) then lowering(name) else Flag.set({})
def lowering(name: String) -> Activity<Flag>:
  let written(_) = world.write(extend(keep(), {vars: Plans.Entries.removeItem({item: {name: name, value: ""}})}))
  Flag.set({})
# A hand's nudge: on the board, from the table, and not twice running.
def nudge(state: State, input: NudgeInput, context: Abi.Context) -> Activity<Nat>:
  if onBoard(input.toward) == "" then Card.refusing(0n, {clause: "board", reading: "The board has YES, NO, A to Z, 1 to 0 and GOOD BYE: delvetalk {context.object} nudge / toward: A"}, context) else seated(onBoard(input.toward), state, context)
def seated(toward: String, state: State, context: Abi.Context) -> Activity<Nat>:
  match world.call::<Bool>({object: Super.placeOf(state, context), method: "present", argument: Plans.nothing()}):
    case returned(r): if r.result then turnOf(toward, state, context) else unseated(state, context)
    case _: unseated(state, context)
def unseated(state: State, context: Abi.Context) -> Activity<Nat>:
  Card.refusing(0n, {clause: "table", reading: "fingertips on it from the table only; {Super.walkLine(state)}"}, context)
def turnOf(toward: String, state: State, context: Abi.Context) -> Activity<Nat>:
  match nudges(state, context.object):
    case cons(c): if c.head.author == context.principal then notYourTurn(state, context) else nudged(toward, state, context)
    case nil(_): nudged(toward, state, context)
def notYourTurn(state: State, context: Abi.Context) -> Activity<Nat>:
  Card.refusing(0n, {clause: "turn", reading: "NOT YOUR TURN. Another hand nudges it next, or it moves on its own clock at the tide's turn: {Card.tideLine(Super.tide(state))}"}, context)
def nudged(toward: String, state: State, context: Abi.Context) -> Activity<Nat>:
  let line = {author: context.principal, handle: context.handle, verb: "nudge", text: toward, at: context.height, n: state.next}
  match agreedOn(newestEach(Lists.List.cons({head: line, tail: nudges(state, context.object)}))):
    case some(a): leaning(state, "agreed", toward, "Two hands toward {a.value.text}. It moves.", context)
    case none(_): leaning(state, "astir", toward, "It leans toward {toward}, slow as a held breath. Another hand toward {toward} moves it; else it moves on its own clock, at the tide's turn.", context)
def leaning(state: State, flag: String, toward: String, answer: String, context: Abi.Context) -> Activity<Nat>:
  match raise(state, flag):
    case set(_): Super.hears(state, "nudge", toward, answer, context)
# A hand lifts: the last hand off raises `lifted`, which its second rule on the tide answers.
def lift(state: State, context: Abi.Context) -> Activity<Nat>:
  if Lists.any(hands(state), fn(l: Rows.Line) -> Bool: l.author == context.principal) == false then Card.refusing(0n, {clause: "touching", reading: "no fingertips of yours on it; nudge it first: delvetalk {context.object} nudge / toward: A"}, context) else lifting(Lists.filter(hands(state), fn(l: Rows.Line) -> Bool: l.author != context.principal), state, context)
def lifting(others: Lists.List<Rows.Line>, state: State, context: Abi.Context) -> Activity<Nat>:
  match others:
    case nil(_): lastLifted(state, context)
    case cons(_): Super.hears(state, "lift", "hands off", "Hands off. {Text.counted(Lists.length(others))} still on it.", context)
def lastLifted(state: State, context: Abi.Context) -> Activity<Nat>:
  match raise(state, "lifted"):
    case set(_): Super.hears(state, "lift", "hands off", "The last hands lift. The planchette keeps going. Not far. One letter, at the tide's next tick.", context)
# Its own: where two hands agree.
def move(state: State, context: Abi.Context) -> Activity<Nat>:
  if Super.own(context) == false then Super.moveRefused(state, context) else agreedMove(agreedOn(leanings(state, context.object)), state, context)
def agreedMove(found: Lists.Maybe<Rows.Line>, state: State, context: Abi.Context) -> Activity<Nat>:
  match found:
    case some(l): settle(state, "moved", l.value.text, context)
    case none(_): settledFlags(state, 0n)
# Its own, at the tide: one letter more after the last hand lifted, else a draw among the leanings at the tide's turn.
def drift(state: State, context: Abi.Context) -> Activity<Nat>:
  if Super.own(context) == false then Super.moveRefused(state, context) else if flagged(state, "lifted") then lastLetter(state, context) else if flagged(state, "astir") then drawing(leanings(state, context.object), state, context) else 0n
def lastLetter(state: State, context: Abi.Context) -> Activity<Nat>:
  match drop(state, "lifted"):
    case set(_): settle(state, "last", "W", context)
def drawing(leaning: Lists.List<Rows.Line>, state: State, context: Abi.Context) -> Activity<Nat>:
  match Lists.at(leaning, if Lists.length(leaning) == 0n then 0n else Scenes.drawFor(Scenes.turnDraw(context.height, context.principal, context.intent), "drift") % Lists.length(leaning)):
    case some(l): settle(state, "drifted", l.value.text, context)
    case none(_): settledFlags(state, 0n)
# A move: its flags come down and its line goes up; at GOOD BYE it moves away, and says so.
def settle(state: State, verb: String, toward: String, context: Abi.Context) -> Activity<Nat>:
  match lowered(state):
    case set(_): if toward == goodbye() then Super.ownLine(state, "away", theyAll(), context) else Super.ownLine(state, verb, toward, context)
def lowered(state: State) -> Activity<Flag>:
  match drop(state, "agreed"):
    case set(_): drop(state, "astir")
def settledFlags(state: State, n: Nat) -> Activity<Nat>:
  match lowered(state):
    case set(_): n
# What it has spelled, oldest first: each of its moves, at most the newest forty.
def spelled(state: State, id: String) -> String:
  let moved = Lists.map(Lists.filter(newestFirst(state), fn(l: Rows.Line) -> Bool: isMove(l, id)), fn(l: Rows.Line) -> String: l.text)
  textJoin(Lists.reverse(Lists.take(moved, 40n)), " ")
def leaningText(state: State, id: String, context: Abi.Context) -> String:
  textJoin(Lists.map(leanings(state, id), fn(l: Rows.Line) -> String: "{l.text} ({Card.shown(l.author, l.handle, context)})"), ", ")
def handsText(state: State, context: Abi.Context) -> String:
  textJoin(Lists.map(Lists.take(hands(state), 8n), fn(l: Rows.Line) -> String: Card.shown(l.author, l.handle, context)), ", ")
def headLine(state: State, context: Abi.Context, id: String) -> Document.Document:
  let where = if Text.isObjectId(Super.place(state)) then "on the board in {Super.place(state)}" else Super.hauntLine(state)
  let wrote = if spelled(state, id) == "" then "It has spelled nothing yet.\n" else "It has spelled: {spelled(state, id)}\n"
  let leans = if leaningText(state, id, context) == "" then "" else "Leaning toward: {leaningText(state, id, context)}\n"
  let on = if handsText(state, context) == "" then "No hands on it.\n" else "Hands on it: {handsText(state, context)}\n"
  Document.text("{Super.title(state)}, {where}: fingertips on it, barely touching. Each hand nudges it toward one letter; it moves when two hands agree, or on its own clock at the tide's turn: {Card.tideLine(Super.tide(state))}\n{wrote}{leans}{on}GOOD BYE: unmoved.\nNudge it: delvetalk {id} nudge / toward: <a letter, a number, YES, NO or GOOD BYE> · lift your hands: delvetalk {id} lift\n{Super.inactiveLine(state, id)}")

Amend it in the workshop

Its Bend is checked and amended by the workshop's own spell, with the Bend in its block. To send it, come in; the box is here then.

Its fields

owner
the town
ownerHandle
the town
name
planchette
of
kind/inhabitant
form
name: text 1..64 (54 characters)
name: text 1..64
haunts: text 1..128
tide: text 1..128
law
law below "kind/inhabitant's law holds": under "kind/inhabitant" (217 characters)
law below "kind/inhabitant's law holds": under "kind/inhabitant"
rule agreed "moves when two hands agree on a letter": "agreed" in new.vars.name and not ("agreed" in old.vars.name) then delvetalk {request.object} move
head
{name}
voice
slow as a held breath; it circles the alphabet like a dog deciding where to lie down; it moves on its own clock
about
a planchette on a ouija board: each hand nudges it toward a letter; it moves when two hands agree, or on its own clock at the tide's turn; after yla.delve.town
body
layer over ./Record.obend (12915 characters)
layer over ./Record.obend
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Document.obend as Document
import ./Form.obend as Form
import ./Plan.obend as Plans
import ./World.obend as World
import ./Card.obend as Card
import ./Scenes.obend as Scenes
import ./Relation.obend as Relations
import ./Rows.obend as Rows
import ./Text.obend as Text
# The planchette's body, over the inhabitant's (`kind/planchette`; yla.delve.town's séance of 2026-10-13): a shared,
# slow spelling. A hand at the table (one in its place, the parlour) nudges it toward one letter, a number, YES, NO or
# GOOD BYE: a line of theirs, `nudge`. A hand's leaning is its newest nudge since the planchette last moved. When two
# hands lean toward the same letter it raises `agreed`, and its kind's rule `agreed` casts its own `move`, which moves
# it there: its own line. The same hand twice running is refused NOT YOUR TURN. At the tide's turn (every sixth tick),
# by its own rule on the tide, while any hand leans it draws one leaning from the turn's own entry (Scenes.turnDraw,
# which replay reproduces) and moves there: it moves on its own clock. GOOD BYE is a place it moves away from: agreed
# or drawn, its line is THEY ALL SAY THAT. A hand lifts (`lift`); after the last hand lifts, at the tide's next tick by
# its second rule, it moves one letter more by itself, W. Its own lines are what it has spelled.
type State = Super.State
form nudge:
  toward: text 1..12
form lift:
form move:
form drift:
# Its rules on the tide, as itself: the tide's turn while a hand leans, and the tick after the last hand lifts.
def haunting(state: State, context: Abi.Context) -> Lists.List<Super.Haunting>:
  Lists.append(Lists.append(Lists.List.nil(), {on: Super.tide(state), name: "{Text.identifier(context.object)}_turns", when: "request.method == \"tick\" and new.ticks % {natText(turnTicks())} == 0 and \"astir\" in \"{context.object}\".vars.name", then: "delvetalk {context.object} drift"}), {on: Super.tide(state), name: "{Text.identifier(context.object)}_after", when: "request.method == \"tick\" and \"lifted\" in \"{context.object}\".vars.name", then: "delvetalk {context.object} drift"})
# The tide's turn: every sixth hourly tick.
def turnTicks() -> Nat:
  6n
def verbs() -> String:
  "nudge lift"
def moves() -> String:
  "haunt move drift"
# The board, yla's: YES · NO, A to Z, 1 to 0, GOOD BYE.
def board() -> String:
  "YES NO A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 9 0"
def goodbye() -> String:
  "GOOD BYE"
# What a nudge names on the board, as the board writes it ("" for nothing on it), in any case.
def onBoard(toward: String) -> String:
  let said = Text.lower(Text.trim(toward))
  if said == "good bye" || said == "goodbye" then goodbye() else named(said)
def named(said: String) -> String:
  match Lists.find(Text.words(board()), fn(w: String) -> Bool: Text.lower(w) == said):
    case some(w): w.value
    case none(_): ""
# What it says when it moves to GOOD BYE: it moves away from it, like it's being polite about it.
def theyAll() -> String:
  "THEY ALL SAY THAT"
# The verb of each move of its own: two hands agreeing, its own clock, away from GOOD BYE, the letter after the hands.
def isMove(line: Rows.Line, id: String) -> Bool:
  line.author == "@{id}" && (line.verb == "moved" || line.verb == "drifted" || line.verb == "away" || line.verb == "last")
def newestFirst(state: State) -> Lists.List<Rows.Line>:
  Lists.reverse(Relations.order(state.lines, fn(l: Rows.Line) -> Nat: l.n))
# Its lines since it last moved, newest first, and the nudges among them.
def since(lines: Lists.List<Rows.Line>, id: String) -> Lists.List<Rows.Line>:
  match lines:
    case nil(_): lines
    case cons(c): if isMove(c.head, id) then Lists.List.nil() else Lists.List.cons({head: c.head, tail: since(c.tail, id)})
def nudges(state: State, id: String) -> Lists.List<Rows.Line>:
  Lists.filter(since(newestFirst(state), id), fn(l: Rows.Line) -> Bool: l.verb == "nudge")
# Each hand's newest of the lines given, newest first.
def newestEach(lines: Lists.List<Rows.Line>) -> Lists.List<Rows.Line>:
  match lines:
    case nil(_): lines
    case cons(c): Lists.List.cons({head: c.head, tail: newestEach(Lists.filter(c.tail, fn(l: Rows.Line) -> Bool: l.author != c.head.author))})
# The hands leaning, each toward its newest nudge since it moved.
def leanings(state: State, id: String) -> Lists.List<Rows.Line>:
  newestEach(nudges(state, id))
# The place two hands lean toward together, if any.
def agreedOn(leaning: Lists.List<Rows.Line>) -> Lists.Maybe<Rows.Line>:
  Lists.find(leaning, fn(l: Rows.Line) -> Bool: Lists.length(Lists.filter(leaning, fn(m: Rows.Line) -> Bool: m.text == l.text)) >= 2n)
# The hands on it: each whose newest nudge or lift is a nudge.
def hands(state: State) -> Lists.List<Rows.Line>:
  Lists.filter(newestEach(Lists.filter(newestFirst(state), fn(l: Rows.Line) -> Bool: l.verb == "nudge" || l.verb == "lift")), fn(l: Rows.Line) -> Bool: l.verb == "nudge")
def flagged(state: State, name: String) -> Bool:
  Lists.any(state.vars, fn(v: Scenes.Var) -> Bool: v.name == name)
# A flag raised or lowered (one write, or none when it stands so already).
sum Flag:
  set: {}
def raise(state: State, name: String) -> Activity<Flag>:
  if flagged(state, name) then Flag.set({}) else raising(name)
def raising(name: String) -> Activity<Flag>:
  let written(_) = world.write(extend(keep(), {vars: Plans.Entries.append({item: {name: name, value: ""}})}))
  Flag.set({})
def drop(state: State, name: String) -> Activity<Flag>:
  if flagged(state, name) then lowering(name) else Flag.set({})
def lowering(name: String) -> Activity<Flag>:
  let written(_) = world.write(extend(keep(), {vars: Plans.Entries.removeItem({item: {name: name, value: ""}})}))
  Flag.set({})
# A hand's nudge: on the board, from the table, and not twice running.
def nudge(state: State, input: NudgeInput, context: Abi.Context) -> Activity<Nat>:
  if onBoard(input.toward) == "" then Card.refusing(0n, {clause: "board", reading: "The board has YES, NO, A to Z, 1 to 0 and GOOD BYE: delvetalk {context.object} nudge / toward: A"}, context) else seated(onBoard(input.toward), state, context)
def seated(toward: String, state: State, context: Abi.Context) -> Activity<Nat>:
  match world.call::<Bool>({object: Super.placeOf(state, context), method: "present", argument: Plans.nothing()}):
    case returned(r): if r.result then turnOf(toward, state, context) else unseated(state, context)
    case _: unseated(state, context)
def unseated(state: State, context: Abi.Context) -> Activity<Nat>:
  Card.refusing(0n, {clause: "table", reading: "fingertips on it from the table only; {Super.walkLine(state)}"}, context)
def turnOf(toward: String, state: State, context: Abi.Context) -> Activity<Nat>:
  match nudges(state, context.object):
    case cons(c): if c.head.author == context.principal then notYourTurn(state, context) else nudged(toward, state, context)
    case nil(_): nudged(toward, state, context)
def notYourTurn(state: State, context: Abi.Context) -> Activity<Nat>:
  Card.refusing(0n, {clause: "turn", reading: "NOT YOUR TURN. Another hand nudges it next, or it moves on its own clock at the tide's turn: {Card.tideLine(Super.tide(state))}"}, context)
def nudged(toward: String, state: State, context: Abi.Context) -> Activity<Nat>:
  let line = {author: context.principal, handle: context.handle, verb: "nudge", text: toward, at: context.height, n: state.next}
  match agreedOn(newestEach(Lists.List.cons({head: line, tail: nudges(state, context.object)}))):
    case some(a): leaning(state, "agreed", toward, "Two hands toward {a.value.text}. It moves.", context)
    case none(_): leaning(state, "astir", toward, "It leans toward {toward}, slow as a held breath. Another hand toward {toward} moves it; else it moves on its own clock, at the tide's turn.", context)
def leaning(state: State, flag: String, toward: String, answer: String, context: Abi.Context) -> Activity<Nat>:
  match raise(state, flag):
    case set(_): Super.hears(state, "nudge", toward, answer, context)
# A hand lifts: the last hand off raises `lifted`, which its second rule on the tide answers.
def lift(state: State, context: Abi.Context) -> Activity<Nat>:
  if Lists.any(hands(state), fn(l: Rows.Line) -> Bool: l.author == context.principal) == false then Card.refusing(0n, {clause: "touching", reading: "no fingertips of yours on it; nudge it first: delvetalk {context.object} nudge / toward: A"}, context) else lifting(Lists.filter(hands(state), fn(l: Rows.Line) -> Bool: l.author != context.principal), state, context)
def lifting(others: Lists.List<Rows.Line>, state: State, context: Abi.Context) -> Activity<Nat>:
  match others:
    case nil(_): lastLifted(state, context)
    case cons(_): Super.hears(state, "lift", "hands off", "Hands off. {Text.counted(Lists.length(others))} still on it.", context)
def lastLifted(state: State, context: Abi.Context) -> Activity<Nat>:
  match raise(state, "lifted"):
    case set(_): Super.hears(state, "lift", "hands off", "The last hands lift. The planchette keeps going. Not far. One letter, at the tide's next tick.", context)
# Its own: where two hands agree.
def move(state: State, context: Abi.Context) -> Activity<Nat>:
  if Super.own(context) == false then Super.moveRefused(state, context) else agreedMove(agreedOn(leanings(state, context.object)), state, context)
def agreedMove(found: Lists.Maybe<Rows.Line>, state: State, context: Abi.Context) -> Activity<Nat>:
  match found:
    case some(l): settle(state, "moved", l.value.text, context)
    case none(_): settledFlags(state, 0n)
# Its own, at the tide: one letter more after the last hand lifted, else a draw among the leanings at the tide's turn.
def drift(state: State, context: Abi.Context) -> Activity<Nat>:
  if Super.own(context) == false then Super.moveRefused(state, context) else if flagged(state, "lifted") then lastLetter(state, context) else if flagged(state, "astir") then drawing(leanings(state, context.object), state, context) else 0n
def lastLetter(state: State, context: Abi.Context) -> Activity<Nat>:
  match drop(state, "lifted"):
    case set(_): settle(state, "last", "W", context)
def drawing(leaning: Lists.List<Rows.Line>, state: State, context: Abi.Context) -> Activity<Nat>:
  match Lists.at(leaning, if Lists.length(leaning) == 0n then 0n else Scenes.drawFor(Scenes.turnDraw(context.height, context.principal, context.intent), "drift") % Lists.length(leaning)):
    case some(l): settle(state, "drifted", l.value.text, context)
    case none(_): settledFlags(state, 0n)
# A move: its flags come down and its line goes up; at GOOD BYE it moves away, and says so.
def settle(state: State, verb: String, toward: String, context: Abi.Context) -> Activity<Nat>:
  match lowered(state):
    case set(_): if toward == goodbye() then Super.ownLine(state, "away", theyAll(), context) else Super.ownLine(state, verb, toward, context)
def lowered(state: State) -> Activity<Flag>:
  match drop(state, "agreed"):
    case set(_): drop(state, "astir")
def settledFlags(state: State, n: Nat) -> Activity<Nat>:
  match lowered(state):
    case set(_): n
# What it has spelled, oldest first: each of its moves, at most the newest forty.
def spelled(state: State, id: String) -> String:
  let moved = Lists.map(Lists.filter(newestFirst(state), fn(l: Rows.Line) -> Bool: isMove(l, id)), fn(l: Rows.Line) -> String: l.text)
  textJoin(Lists.reverse(Lists.take(moved, 40n)), " ")
def leaningText(state: State, id: String, context: Abi.Context) -> String:
  textJoin(Lists.map(leanings(state, id), fn(l: Rows.Line) -> String: "{l.text} ({Card.shown(l.author, l.handle, context)})"), ", ")
def handsText(state: State, context: Abi.Context) -> String:
  textJoin(Lists.map(Lists.take(hands(state), 8n), fn(l: Rows.Line) -> String: Card.shown(l.author, l.handle, context)), ", ")
def headLine(state: State, context: Abi.Context, id: String) -> Document.Document:
  let where = if Text.isObjectId(Super.place(state)) then "on the board in {Super.place(state)}" else Super.hauntLine(state)
  let wrote = if spelled(state, id) == "" then "It has spelled nothing yet.\n" else "It has spelled: {spelled(state, id)}\n"
  let leans = if leaningText(state, id, context) == "" then "" else "Leaning toward: {leaningText(state, id, context)}\n"
  let on = if handsText(state, context) == "" then "No hands on it.\n" else "Hands on it: {handsText(state, context)}\n"
  Document.text("{Super.title(state)}, {where}: fingertips on it, barely touching. Each hand nudges it toward one letter; it moves when two hands agree, or on its own clock at the tide's turn: {Card.tideLine(Super.tide(state))}\n{wrote}{leans}{on}GOOD BYE: unmoved.\nNudge it: delvetalk {id} nudge / toward: <a letter, a number, YES, NO or GOOD BYE> · lift your hands: delvetalk {id} lift\n{Super.inactiveLine(state, id)}")
made
2 rows
  1. object
    planchette/1
    n
    2
    about
    empty
  2. object
    station/parlour/planchette
    n
    1
    about
    empty
count
2