DelveTalkcome in

the modules · from entry 2325 · bafyreif26e62m6x5ag2wozarv5wmi2gxpekqe7nzqmunvjhwzfzxdt542i

Avatar

Avatar.obend 514 lines
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Document.obend as Document
import ./Plan.obend as Plans
import ./Form.obend as Form
import ./Card.obend as Card
import ./Relation.obend as Relations
import ./Rows.obend as Rows
import ./Scenes.obend as Scenes
import ./Text.obend as Text
import ./World.obend as World
type Done = Scenes.Done
# A note in the inbox: who it is from (a principal, or the object whose rule told of a change),
# their handle when the host knew it, the line, the height its turn read, `n`, the inbox's
# count then, and `uri`, the post it mentions the avatar in ("" for any other note). Keyed
# {at, from, n}, so key order (at first) is the order they came; the inbox keeps 64, the host
# dropping the oldest.
record Note:
  from: String
  handle: String
  text: String
  at: Nat
  n: Nat
  uri: String
# What another hand did to a thing this avatar's principal made (`heard`): the thing, the verb (a
# rain, a write, a grown verb, an inspiriting, a door hung to it), who did it and their handle then,
# the height, and `n`, the count then. Keyed {at, n}; the newest 64 are kept.
record Heard:
  thing: String
  verb: String
  by: String
  handle: String
  at: Nat
  n: Nat
# The object a verified principal gets; its id is the principal's DID string. What it is told
# lands in `inbox`: rule notes, tide notes, and the posts that mention it; what others did to the
# things it made lands in `heard`, while `hear` is on. `seen` is the entry its principal has read
# through: the notes and the heard after it are the new ones.
record State:
  handle: String
  # The index maintained by room presence transactions.
  at: Plans.Reference
  holding: Lists.List<Plans.Reference>
  inbox: Relations.Relation<Note>
  heard: Relations.Relation<Heard>
  hear: Bool
  seen: Nat
  tongue: String
# Only its own methods write it: a write no method made is refused (`methods`), and a rule is
# placed on it by its principal alone (the object's id is the principal's DID). The predicate, not
# a text law, so the host's default law (its creator alone reprograms or amends it) stands beside it.
def law(old: State, new: State, request: Abi.Request) -> Abi.Verdict:
  if request.kind == 3n || (request.kind == 0n && request.method == "") then Abi.Verdict.refused({clause: "methods", reading: "Only its own methods write it; a proposed write is refused."}) else if request.kind == 4n && request.context.principal != request.context.object then Abi.Verdict.refused({clause: "rules", reading: "Only its principal places a rule on it."}) else Abi.Verdict.admitted({})
# The avatar as edition 10 keeps it (a carry lays this package over it, `migrate`): no tongue yet.
record State10:
  handle: String
  at: Plans.Reference
  holding: Lists.List<Plans.Reference>
  inbox: Relations.Relation<Note>
  heard: Relations.Relation<Heard>
  hear: Bool
  seen: Nat
def migrate(old: State10) -> State:
  {handle: old.handle, at: old.at, holding: old.holding, inbox: old.inbox, heard: old.heard, hear: old.hear, seen: old.seen, tongue: ""}
def initial() -> State:
  {handle: "", at: Plans.nobody(), holding: Lists.List.nil(), inbox: Relations.empty(), heard: Relations.empty(), hear: true, seen: 0n, tongue: ""}
def relations() -> Relations.Decls:
  Lists.append(Lists.append(Relations.Decls.nil(), {field: "inbox", key: Text.words("at from n"), limit: 64n}), {field: "heard", key: Text.words("at n"), limit: 64n})
# An avatar answers only to its own principal: its id is the principal's DID.
def mine(context: Abi.Context) -> Bool:
  context.principal == context.object
# Who the avatar is, as a refusal and its card name it: the town as such, else its handle, else the shortened DID.
def owner(state: State, context: Abi.Context) -> String:
  Card.shown(context.object, state.handle, context)
sum Why:
  notMine: {who: String, doing: String}
  notAThing: {doing: String}
  noAnswer: {from: String}
  alreadySeen: {mark: Nat}
  noExit: {label: String}
  roomSilent: {}
  nowhere: {}
  notTold: {}
  notTown: {}
def refusal(w: Why) -> Card.Refusal:
  match w:
    case notMine(n): {clause: "notMine", reading: "Only {n.who} {n.doing}."}
    case notAThing(n): {clause: "notAThing", reading: "Only a thing can be {n.doing}."}
    case noAnswer(n): {clause: "noAnswer", reading: "No answer from {n.from}"}
    case alreadySeen(s): {clause: "alreadySeen", reading: "Already read through entry {natText(s.mark)}."}
    case noExit(n): {clause: "noExit", reading: "There is no exit named {n.label}"}
    case roomSilent(_): {clause: "roomSilent", reading: "The room did not answer."}
    case nowhere(_): {clause: "nowhere", reading: "Nowhere yet; go to a room first: delvetalk me go / to: <room>."}
    case notTold(_): {clause: "heard", reading: "Only a thing tells its maker what another hand did to it."}
    case notTown(_): {clause: "notTown", reading: "Only the town tells an avatar the posts that mentioned it."}
def refused(w: Why) -> Done:
  Done.refused(refusal(w))
# A refusal, answered by name (Card.refusing), and nothing written.
def told(w: Why, context: Abi.Context) -> Activity<Done>:
  Card.refusing(refused(w), refusal(w), context)
def answerRefused(refusal: Card.Refusal, context: Abi.Context) -> Activity<Card.Replied>:
  Card.refusing(Card.Replied.refused(refusal), refusal, context)
# --- The inbox ---------------------------------------------------------------------
# A note is anyone's line, or a fired rule's: a rule's spell `delvetalk me note / text: ...` runs
# under the rule's owner on the owner's avatar. A note someone sends by spell is answered; one an
# object started (a rule, a tide's tick) is not.
def note(state: State, input: {text: String}, context: Abi.Context) -> Activity<Done>:
  match noted({from: context.principal, handle: context.handle, text: input.text, at: context.height, n: Relations.count(state.inbox), uri: ""}):
    case done(_): if context.caller == "" then Card.tell(Done.done({}), Document.text("Noted on {owner(state, context)}'s avatar.\n")) else Done.done({})
    case _: Done.done({})
def noted(note: Note) -> Activity<Done>:
  let written(_) = write {inbox: insert note}
  Done.done({})
# A post that mentions the avatar's resident, addressed to it as the author's turn: a note from
# the author, the post its `uri`, and no answer (no offer).
def mentioned(state: State, input: Card.Reply, context: Abi.Context) -> Activity<Card.Replied>:
  let written(_) = write {inbox: insert {from: context.principal, handle: context.handle, text: textTake(input.text, 280n), at: context.height, n: Relations.count(state.inbox), uri: input.post}}
  Card.Replied.silent({})
# The posts elsewhere that mentioned its resident, told by the town (the bridge's notes of the hour, a turn of the
# town's for each resident it heard of): each a note from the post's author under their handle, its `uri` the post,
# the first 280 characters kept. Only the town tells them (`context.town`); anyone else's are refused, nothing written.
record Mention:
  from: String
  handle: String
  text: String
  post: String
def mentions(state: State, input: {notes: Lists.List<Mention>}, context: Abi.Context) -> Activity<Done>:
  if context.town == "" || context.principal != context.town || context.caller != "" then told(Why.notTown({}), context) else telling(input.notes, Relations.count(state.inbox), context)
def telling(notes: Lists.List<Mention>, n: Nat, context: Abi.Context) -> Activity<Done>:
  match notes:
    case nil(_): Done.done({})
    case cons(c): tellingOne(c.head, c.tail, n, context)
def tellingOne(note: Mention, rest: Lists.List<Mention>, n: Nat, context: Abi.Context) -> Activity<Done>:
  let written(_) = write {inbox: insert {from: note.from, handle: note.handle, text: textTake(note.text, 280n), at: context.height, n: n, uri: note.post}}
  telling(rest, n + 1n, context)
# A thing tells its maker what another hand did to it, by a send run as that hand: a line written on a
# record (a write, a rain, a pour, a grown verb), a grown verb used or an inspiriting on a THING, a door
# a record hangs to it. Only a thing tells (the turn's sender), and only of a thing this avatar's
# principal made (a record's `maker`, else its `owner`); a hand's `heard` is refused by name. With
# `hear` off it is taken as nothing. The sending object is the thing the notice names.
def heard(state: State, input: {thing: String, verb: String}, context: Abi.Context) -> Activity<Done>:
  if context.sender == "" || context.sender != input.thing then told(Why.notTold({}), context) else if state.hear == false || context.principal == context.object then Done.done({}) else making(state, input, context)
def making(state: State, input: {thing: String, verb: String}, context: Abi.Context) -> Activity<Done>:
  match world.viewField::<String>({object: {world: context.world, object: input.thing}, field: "maker"}):
    case viewed(v): if v.state == "" then owning(state, input, context) else madeBy(v.state, state, input, context)
    case _: owning(state, input, context)
def owning(state: State, input: {thing: String, verb: String}, context: Abi.Context) -> Activity<Done>:
  match world.viewField::<String>({object: {world: context.world, object: input.thing}, field: "owner"}):
    case viewed(v): madeBy(v.state, state, input, context)
    case _: Done.done({})
def madeBy(maker: String, state: State, input: {thing: String, verb: String}, context: Abi.Context) -> Activity<Done>:
  if maker != context.object then Done.done({}) else hearing(state, input, context)
def hearing(state: State, input: {thing: String, verb: String}, context: Abi.Context) -> Activity<Done>:
  let written(_) = write {heard: insert {thing: input.thing, verb: input.verb, by: context.principal, handle: context.handle, at: context.height, n: Relations.count(state.heard)}}
  Done.done({})
# --- Reading -----------------------------------------------------------------------
# Its principal marks the entry it has read through; the notes after it are the new ones.
def seen(state: State, input: {at: Nat}, context: Abi.Context) -> Activity<Done>:
  if mine(context) == false then told(Why.notMine({who: owner(state, context), doing: "marks it read"}), context) else if input.at < state.seen then told(Why.alreadySeen({mark: state.seen}), context) else marked(state, input.at, context)
def marked(state: State, at: Nat, context: Abi.Context) -> Activity<Done>:
  let written(_) = write {seen: set at}
  Card.tell(Done.done({}), Document.Document.sequence({items: Document.Documents.cons({head: Document.text("Read through entry {natText(at)}; nothing before it is new again.\n"), tail: Document.Documents.cons({head: render(extend(state, {seen: at}), context), tail: Document.Documents.nil()})})}))
# The card, asked for: since you were here, then what it holds and the new notes. Writes nothing.
def since(state: State, context: Abi.Context) -> Activity<Done>:
  Card.tell(Done.done({}), render(state, context))
# --- Holding -------------------------------------------------------------------------
# Things call hold and release on their holder's avatar. Both are idempotent and never
# abort a thing's turn, so a thing that has already staged its own writes cannot be left half
# done. The thing is the calling object. Only the avatar's own principal's turn holds (a
# pick-up is the taker's, an accepted offer the accepter's): another's thing is answered
# notMine and recorded nowhere.
def thing(context: Abi.Context) -> Plans.Reference:
  {world: context.world, object: context.caller}
def hold(state: State, context: Abi.Context) -> Activity<Done>:
  if context.caller == "" then refused(Why.notAThing({doing: "held"})) else if mine(context) == false then refused(Why.notMine({who: owner(state, context), doing: "takes what it holds"})) else if Lists.any(state.holding, fn(h: Plans.Reference) -> Bool: Plans.same(h, thing(context))) then Done.done({}) else held(context)
def held(context: Abi.Context) -> Activity<Done>:
  let written(_) = write {holding: append thing(context)}
  Done.done({})
def release(state: State, context: Abi.Context) -> Activity<Done>:
  if context.caller == "" then refused(Why.notAThing({doing: "released"})) else if Lists.any(state.holding, fn(h: Plans.Reference) -> Bool: Plans.same(h, thing(context))) then released(context) else Done.done({})
def released(context: Abi.Context) -> Activity<Done>:
  let written(_) = write {holding: removeItem thing(context)}
  Done.done({})
# Accept what someone offered: the thing's transfer (judged by its own law) moves custody to
# this avatar, and this avatar records it in its own holding, in the same turn.
def accept(state: State, input: {thing: String}, context: Abi.Context) -> Activity<Done>:
  if mine(context) == false then told(Why.notMine({who: owner(state, context), doing: "accepts for it"}), context) else accepting({world: context.world, object: input.thing}, context)
def accepting(thing: Plans.Reference, context: Abi.Context) -> Activity<Done>:
  match world.call::<Done>({object: thing, method: "transfer", argument: Plans.nothing()}):
    case returned(r): accepted(r.result, thing, context)
    case _: told(Why.noAnswer({from: thing.object}), context)
def accepted(result: Done, thing: Plans.Reference, context: Abi.Context) -> Activity<Done>:
  match result:
    case refused(r): Card.refusing(result, r, context)
    case _: recorded(thing)
def recorded(thing: Plans.Reference) -> Activity<Done>:
  let written(_) = write {holding: append thing}
  Card.tell(Done.done({}), Document.text("Accepted {thing.object}; you hold it.\n"))
# --- Moving --------------------------------------------------------------------------
sum Moved:
  moved: {to: Plans.Reference}
  refused: Card.Refusal
def moveRefused(w: Why, context: Abi.Context) -> Activity<Moved>:
  movedRefused(refusal(w), context)
def movedRefused(refusal: Card.Refusal, context: Abi.Context) -> Activity<Moved>:
  Card.refusing(Moved.refused(refusal), refusal, context)
# Room presence is authoritative. `at` indexes it for the card and scoped commands; a room's
# entry or departure calls `locate` in the same turn. The room answers `present` from its staged
# state, so the index includes an entry or END that has not committed yet.
def locate(state: State, context: Abi.Context) -> Activity<Bool>:
  if mine(context) == false || context.caller == "" then refuse("refused location: Only a room in its resident's turn indexes presence.") else roomLocated(state, {world: context.world, object: context.caller}, context)
def roomLocated(state: State, room: Plans.Reference, context: Abi.Context) -> Activity<Bool>:
  match world.viewField::<Relations.Relation<Rows.Presence>>({object: room, field: "presence"}):
    case viewed(_): presenceLocated(state, room, context)
    case denied(_): refuse("refused location: The calling room has no readable presence.")
    case refused(_): refuse("refused location: The calling room has no readable presence.")
def presenceLocated(state: State, room: Plans.Reference, context: Abi.Context) -> Activity<Bool>:
  match world.call::<Bool>({object: room, method: "present", argument: Plans.nothing()}):
    case returned(r): if r.result then relocating(state.at, room, context) else if Plans.same(state.at, room) then located(Plans.nobody()) else true
    case denied(_): refuse("refused location: The calling room did not answer its presence.")
    case refused(_): refuse("refused location: The calling room did not answer its presence.")
def relocating(from: Plans.Reference, to: Plans.Reference, context: Abi.Context) -> Activity<Bool>:
  if Plans.isNobody(from) || Plans.same(from, to) then located(to) else departingFrom(from, to, context)
def departingFrom(from: Plans.Reference, to: Plans.Reference, context: Abi.Context) -> Activity<Bool>:
  match world.call::<Bool>({object: from, method: "present", argument: Plans.nothing()}):
    case returned(r): if r.result then leavingFor(from, to, context) else located(to)
    case denied(_): refuse("refused location: The previous room did not answer its presence.")
    case refused(_): refuse("refused location: The previous room did not answer its presence.")
def leavingFor(from: Plans.Reference, to: Plans.Reference, context: Abi.Context) -> Activity<Bool>:
  match world.call::<Done>({object: from, method: "leave", argument: Plans.nothing()}):
    case returned(r): locationLeft(r.result, to)
    case refused(r): refuse("refused {r.clause}: {r.reading}")
    case denied(_): refuse("refused location: The previous room did not admit departure.")
def locationLeft(result: Done, to: Plans.Reference) -> Activity<Bool>:
  match result:
    case refused(r): refuse("refused {r.clause}: {r.reading}")
    case done(_): located(to)
    case entered(_): located(to)
    case moved(_): located(to)
    case left(_): located(to)
def located(at: Plans.Reference) -> Activity<Bool>:
  let written(_) = write {at: set at}
  true
# Walk through a door of the room it is in: read its doors (`cites`) and enter the room the
# door cites. Entry performs departure and indexes presence in the same transaction.
def move(state: State, input: {exit: String}, context: Abi.Context) -> Activity<Moved>:
  if mine(context) == false then moveRefused(Why.notMine({who: owner(state, context), doing: "moves it"}), context) else if Plans.isNobody(state.at) then moveRefused(Why.nowhere({}), context) else looked(input.exit, state, context)
def looked(label: String, state: State, context: Abi.Context) -> Activity<Moved>:
  match world.viewField::<Card.Doorways>({object: state.at, field: "cites"}):
    case viewed(v): through(label, Lists.find(v.state, fn(e: Card.Doorway) -> Bool: e.label == label), context)
    case denied(_): moveRefused(Why.noExit({label: label}), context)
    case refused(_): moveRefused(Why.noExit({label: label}), context)
def through(label: String, found: Lists.Maybe<Card.Doorway>, context: Abi.Context) -> Activity<Moved>:
  match found:
    case none(_): moveRefused(Why.noExit({label: label}), context)
    case some(s): entering(s.value.to, context)
def entering(to: Plans.Reference, context: Abi.Context) -> Activity<Moved>:
  match world.call::<Done>({object: to, method: "enter", argument: Plans.nothing()}):
    case returned(r): entered(r.result, to, context)
    case denied(_): moveRefused(Why.roomSilent({}), context)
    case refused(_): moveRefused(Why.roomSilent({}), context)
def entered(result: Done, to: Plans.Reference, context: Abi.Context) -> Activity<Moved>:
  match result:
    case refused(r): movedRefused(r, context)
    case done(_): Moved.moved({to: to})
    case entered(_): Moved.moved({to: to})
    case moved(_): Moved.moved({to: to})
    case left(_): Moved.moved({to: to})
# Go to a room by its id, from anywhere, through that room's entry transaction.
def go(state: State, input: {to: String}, context: Abi.Context) -> Activity<Moved>:
  if mine(context) == false then moveRefused(Why.notMine({who: owner(state, context), doing: "moves it"}), context) else entering({world: context.world, object: input.to}, context)
# --- The card ------------------------------------------------------------------------
form move:
  exit: text 1..64
form go:
  to: text 1..160
form note:
  text: text 1..280
form accept:
  thing: text 1..160
form seen:
  at: natural 0..1000000000
form since:
# Things call hold and release; rooms index presence with locate.
def methods() -> Lists.List<String>:
  Text.words("hold release ~heard ~locate ~mentions")
# Its principal's lenses: the handle the card shows (`delvetalk me set / handle: ...`), and whether
# it hears what others do to the things they made (`delvetalk me set / hear: no`), and the language
# its principal states (`delvetalk me set / tongue: ja`), one of the town's tongues, which it answers in.
def lenses() -> Lists.List<Form.Field>:
  Lists.append(Lists.append(Lists.append(Lists.List.nil(), {name: "handle", kind: Form.Kind.text({min: 1n, max: 64n})}), {name: "hear", kind: Form.Kind.choice({options: Lists.append(Lists.append(Form.Names.nil(), "yes"), "no")})}), {name: "tongue", kind: Form.Kind.choice({options: tongues()})})
# The tongues the town paints, checks and speaks hob's lines in (spec/Delvetalk/Host/Tongue.lean `tongues`).
def tongues() -> Form.Names:
  Lists.append(Lists.append(Lists.append(Lists.append(Lists.append(Lists.append(Form.Names.nil(), "en"), "ja"), "zh"), "es"), "ru"), "ar")
def set(state: State, input: {field: String, value: Form.Value}, context: Abi.Context) -> Activity<Done>:
  if mine(context) == false then told(Why.notMine({who: owner(state, context), doing: "changes it"}), context) else if input.field == "hear" then setHear(Card.valueText(input.value) == "yes") else if input.field == "tongue" then setTongue(Card.valueText(input.value)) else setHandle(Card.valueText(input.value))
def setHandle(handle: String) -> Activity<Done>:
  let written(_) = write {handle: set handle}
  Card.tell(Done.done({}), Document.text("Set handle to {handle}.\n"))
def setTongue(tongue: String) -> Activity<Done>:
  let written(_) = write {tongue: set tongue}
  Card.tell(Done.done({}), Document.text("Set tongue to {tongue}.\n"))
def setHear(hear: Bool) -> Activity<Done>:
  let written(_) = write {hear: set hear}
  Card.tell(Done.done({}), Document.text(if hear then "Hearing again what others do to what you made.\n" else "No longer hearing what others do to what you made: delvetalk me set / hear: yes\n"))
# Scoped resolution: the avatar's own principal's prose with no spell that starts with an
# action word (`acquire the stone`, `bump counter`, `look`) names its object by the first word
# that is a thing lying in the room the avatar last entered (by its name, then by its id's last
# segment), else an object of that name (garden, rooms); the avatar sends the object the matching
# form as a spell, the rest of the line filling its first field. Several things of that name are
# answered with their ids; `look` shows the room as its reader sees it.
# Prose from anyone else is a mention: a note in the inbox, answered with nothing. Anything else is
# the card default.
def receive(state: State, input: Card.Reply, context: Abi.Context) -> Activity<Card.Replied>:
  if Card.quietly(Card.unanswered(input, forms())) then (if mine(context) then scoped(input, Text.words(firstLine(input.text)), context, state) else mentioned(state, input, context)) else answered(input, context, state)
def answered(input: Card.Reply, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  Card.answer(input, context, forms(), render(state, context))
def firstLine(text: String) -> String:
  textTake(text, textBreak(text, "\n"))
def bare(word: String) -> String:
  Card.stripped(word, textLength(word))
def scoped(input: Card.Reply, words: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match words:
    case nil(_): answered(input, context, state)
    case cons(c): if bare(c.head) == "look" then looking(state, context) else placing(input, bare(c.head), c.tail, context, state)
def looking(state: State, context: Abi.Context) -> Activity<Card.Replied>:
  match world.card({object: state.at}):
    case carded(c): Card.tell(Card.Replied.done({action: "look"}), c.document)
    case _: answerRefused(refusal(Why.nowhere({})), context)
record Named:
  object: Plans.Reference
  name: String
record Asked:
  input: Card.Reply
  action: String
  rest: Lists.List<String>
def placing(input: Card.Reply, action: String, rest: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match world.viewField::<Relations.Relation<Plans.Reference>>({object: state.at, field: "things"}):
    case viewed(v): naming({input: input, action: action, rest: rest}, Relations.rows(v.state), Lists.List.nil(), context, state)
    case _: doors({input: input, action: action, rest: rest}, rest, context, state)
# Each thing lying here by the first line of its card (its name).
def naming(asked: Asked, things: Lists.List<Plans.Reference>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match things:
    case nil(_): nouns(asked, asked.rest, named, context, state)
    case cons(c): carding(asked, c.head, c.tail, named, context, state)
def carding(asked: Asked, thing: Plans.Reference, things: Lists.List<Plans.Reference>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match world.card({object: thing}):
    case carded(c): naming(asked, things, Lists.List.cons({head: {object: thing, name: firstLine(Document.plain(c.document))}, tail: named}), context, state)
    case _: naming(asked, things, Lists.List.cons({head: {object: thing, name: ""}, tail: named}), context, state)
def suffix(id: String, word: String) -> Bool:
  id == word || textLength(id) > textLength(word) && textDrop(id, textLength(id) - textLength(word) - 1n) == "/{word}"
def matching(named: Lists.List<Named>, word: String) -> Lists.List<Named>:
  if Lists.any(named, fn(n: Named) -> Bool: n.name == word) then Lists.filter(named, fn(n: Named) -> Bool: n.name == word) else Lists.filter(named, fn(n: Named) -> Bool: suffix(n.object.object, word))
# The first word naming a thing here decides; the words after it fill the form.
def nouns(asked: Asked, words: Lists.List<String>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match words:
    case nil(_): doors(asked, asked.rest, context, state)
    case cons(c): chosen(asked, c.tail, matching(named, bare(c.head)), named, context, state)
def chosen(asked: Asked, after: Lists.List<String>, found: Lists.List<Named>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match found:
    case nil(_): nouns(asked, after, named, context, state)
    case cons(c): if Lists.length(c.tail) > 0n then answerRefused({clause: "ambiguous", reading: "which one: {textJoin(Lists.map(found, fn(n: Named) -> String: n.object.object), \", \")}?"}, context) else acting(asked, c.head.object, after, context, state)
# Else an object named by a word (the commonplace's doors are named by their ids).
def doors(asked: Asked, words: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match words:
    case nil(_): answered(asked.input, context, state)
    case cons(c): doorNamed(asked, {world: context.world, object: bare(c.head)}, c.tail, context, state)
def doorNamed(asked: Asked, door: Plans.Reference, after: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match world.inspect({object: door, source: false}):
    case inspected(i): if Lists.any(i.methods, fn(f: Form.Form) -> Bool: f.action == asked.action) then sending(asked, door, after, i.methods) else doors(asked, after, context, state)
    case _: doors(asked, after, context, state)
def acting(asked: Asked, object: Plans.Reference, after: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
  match world.inspect({object: object, source: false}):
    case inspected(i): if Lists.any(i.methods, fn(f: Form.Form) -> Bool: f.action == asked.action) then sending(asked, object, after, i.methods) else answerRefused({clause: "noAction", reading: "{object.object} does not take {asked.action}; delvetalk {object.object} ?"}, context)
    case _: answered(asked.input, context, state)
# The form as a spell sent to the object's receive, under the speaker; its card answers.
def sending(asked: Asked, object: Plans.Reference, after: Lists.List<String>, forms: Lists.List<Form.Form>) -> Activity<Card.Replied>:
  match world.send({object: object, method: "receive", argument: {text: spellFor(asked.action, object, textJoin(after, " "), forms), post: asked.input.post}}):
    case _: Card.Replied.done({action: asked.action})
def spellFor(action: String, object: Plans.Reference, rest: String, forms: Lists.List<Form.Form>) -> String:
  match Lists.find(forms, fn(f: Form.Form) -> Bool: f.action == action):
    case some(f): "delvetalk {object.object} {action}{fieldLine(f.value.fields, rest)}"
    case none(_): "delvetalk {object.object} {action}"
def fieldLine(fields: Form.Fields, rest: String) -> String:
  match fields:
    case cons(c): if rest == "" then "" else "\n{c.head.name}: {rest}"
    case nil(_): ""
def holdingLine(item: Plans.Reference) -> Document.Document:
  Document.text("Holding: {item.object}\n")
# `#<entry> <who>: <the first line, cut at 100>`
def noteLine(item: Note, context: Abi.Context) -> Document.Document:
  Document.text("#{natText(item.at)} {Card.shown(item.from, item.handle, context)}: {textTake(firstLine(item.text), 100n)}\n")
def fresh(state: State) -> Lists.List<Note>:
  Lists.reverse(Lists.filter(Relations.rows(state.inbox), fn(n: Note) -> Bool: n.at > state.seen))
# To its principal the card leads with what others did to what they made since they were here, one
# line a thing, newest first; then where it is, what it holds and the new notes. Eight of each list,
# then a count; one budget across the card (Card.limit(context)): each list gets what the ones before
# it leave.
def render(state: State, context: Abi.Context) -> Document.Document:
  let since = sinceFor(state, context)
  let head = Document.text("{owner(state, context)}'s avatar, {whereLine(state, context)}.\n{bookLine(state, context)}")
  let held = Card.clippedWithin(Lists.map(state.holding, holdingLine), 8n, Card.left(context, sized(since) + Document.size(head) + 200n))
  let notes = inboxFor(state, context, sized(since) + Document.size(head) + sized(held))
  Document.Document.sequence({items: Lists.concat(Lists.concat(since, Document.Documents.cons({head: head, tail: held})), notes)})
# --- Since you were here ------------------------------------------------------------
def freshHeard(state: State) -> Lists.List<Heard>:
  Lists.reverse(Lists.filter(Relations.rows(state.heard), fn(h: Heard) -> Bool: h.at > state.seen))
# Each value once, in the order it first stands.
def distinct(items: Lists.List<String>) -> Lists.List<String>:
  Lists.fold(items, Lists.List.nil(), fn(x: String) -> Lists.List<String> -> Lists.List<String>: fn(rest: Lists.List<String>) -> Lists.List<String>: Lists.List.cons({head: x, tail: Lists.filter(rest, fn(y: String) -> Bool: y != x)}))
def sinceFor(state: State, context: Abi.Context) -> Document.Documents:
  if Card.reads(context.object, context) == false then Document.Documents.nil() else if state.hear == false then Document.Documents.cons({head: Document.text("Not hearing what others do to what you made: delvetalk me set / hear: yes\n"), tail: Document.Documents.nil()}) else heardFor(state, freshHeard(state), context)
def heardFor(state: State, news: Lists.List<Heard>, context: Abi.Context) -> Document.Documents:
  if Lists.length(news) == 0n then Document.Documents.nil() else sinceLines(state, news, context)
def sinceLines(state: State, news: Lists.List<Heard>, context: Abi.Context) -> Document.Documents:
  let top = Document.text("Since you were here (entry {natText(state.seen)}):\n")
  let lines = Lists.map(distinct(Lists.map(news, fn(h: Heard) -> String: h.thing)), fn(thing: String) -> Document.Document: thingLine(thing, Lists.filter(news, fn(h: Heard) -> Bool: h.thing == thing)))
  Document.Documents.cons({head: top, tail: Card.clippedWithin(lines, 8n, Card.left(context, Document.size(top) + 400n))})
# `  bell/1: 3 rains from kestrel.delve.town and 1 other; 1 door from yue.delve.town`
def thingLine(thing: String, rows: Lists.List<Heard>) -> Document.Document:
  let parts = Lists.map(distinct(Lists.map(rows, fn(h: Heard) -> String: h.verb)), fn(verb: String) -> String: verbPart(verb, Lists.filter(rows, fn(h: Heard) -> Bool: h.verb == verb)))
  let said = textJoin(parts, "; ")
  Document.text("  {thing}: {said}\n")
def verbPart(verb: String, rows: Lists.List<Heard>) -> String:
  let count = Lists.length(rows)
  let others = Lists.length(distinct(Lists.map(rows, fn(h: Heard) -> String: h.by))) - 1n
  let rest = if others == 0n then "" else if others == 1n then " and 1 other" else " and {natText(others)} others"
  let plural = if count == 1n then "" else "s"
  "{natText(count)} {verb}{plural} from {handOf(rows)}{rest}"
def handOf(rows: Lists.List<Heard>) -> String:
  match rows:
    case cons(c): if c.head.handle == "" then Card.handle(c.head.by) else c.head.handle
    case nil(_): ""
# Where it is: what it last entered; nowhere yet, its principal is shown the spell that goes.
def whereLine(state: State, context: Abi.Context) -> String:
  if Plans.isNobody(state.at) == false then "in {state.at.object}" else if Card.reads(context.object, context) then "nowhere yet; go: delvetalk me go / to: <a room>" else "nowhere yet"
# The one line that opens its book (`book`): its principal's by `me`, anyone else's by its handle.
# The spell that reads its principal's book, by the handle a spell names it by: none for the town, which no spell names.
def bookLine(state: State, context: Abi.Context) -> String:
  if Card.reads(context.object, context) then "Its book: delvetalk me book\n" else if state.handle == "" || Card.town(context.object, context) then "" else "Its book: delvetalk {state.handle} book\n"
def sized(lines: Document.Documents) -> Nat:
  Document.size(Document.Document.sequence({items: lines}))
# The notes are the avatar's principal's alone: the new ones after its mark, newest first, and
# the spell that moves the mark. Anyone else sees how many there are.
# The newest entry among the new notes and the new heard (0 when there are none): what `seen` moves to.
def latest(state: State) -> Nat:
  Lists.fold(Lists.concat(Lists.map(fresh(state), fn(n: Note) -> Nat: n.at), Lists.map(freshHeard(state), fn(h: Heard) -> Nat: h.at)), 0n, fn(at: Nat) -> Nat -> Nat: fn(most: Nat) -> Nat: if at > most then at else most)
def inboxFor(state: State, context: Abi.Context, used: Nat) -> Document.Documents:
  if Card.reads(context.object, context) then newFor(state, fresh(state), latest(state), context, used) else Document.Documents.cons({head: Document.text(if Relations.count(state.inbox) == 1n then "1 note\n" else "{natText(Relations.count(state.inbox))} notes\n"), tail: Document.Documents.nil()})
# The new notes, then the spell that marks them and the heard read.
def newFor(state: State, news: Lists.List<Note>, at: Nat, context: Abi.Context, used: Nat) -> Document.Documents:
  let spell = Document.text("  delvetalk me seen / at: {natText(at)}\n")
  let marked = if at == 0n then Document.Documents.nil() else Document.Documents.cons({head: spell, tail: Document.Documents.nil()})
  match news:
    case nil(_): Document.Documents.cons({head: Document.text("No new notes since entry {natText(state.seen)}.\n"), tail: marked})
    case cons(_): newNotes(state, news, marked, context, used + Document.size(spell))
def newNotes(state: State, news: Lists.List<Note>, marked: Document.Documents, context: Abi.Context, used: Nat) -> Document.Documents:
  let top = Document.text("{natText(Lists.length(news))} new since entry {natText(state.seen)}:\n")
  Document.Documents.cons({head: top, tail: Lists.concat(Card.clippedWithin(Lists.map(news, fn(n: Note) -> Document.Document: noteLine(n, context)), 8n, Card.left(context, used + Document.size(top))), marked)})
# --- The book --------------------------------------------------------------------------
# Its principal's commonplace book (docs/VOICE.md "The book, the history, the map"): what they made, each with
# what others did to it (`heard`: rains, doors, uses, by whom), the verbs they grew and on what, the rules and
# wants they placed, the lines they wrote and where, the places they visited; counts and hands, never a score.
# The host hands it what the store shows of them as the reader may view it (`World.Deeds`, Surfaces.deeds) for
# `delvetalk me book`, `delvetalk <handle> book` and the page /o/<handle>/book, and writes nothing: it is read,
# a page within a reply (Card.room), the next page's spell under it.
def book(state: State, context: Abi.Context, deeds: World.Deeds) -> Document.Document:
  let who = owner(state, context)
  let spell = if Card.reads(context.object, context) then "delvetalk me book" else "delvetalk {who} book"
  let lines = bookLines(state, deeds.rows)
  let body = if Lists.length(lines) == 0n then Lists.List.cons({head: "Nothing in it yet. The doors: delvetalk map\n", tail: Lists.List.nil()}) else lines
  Card.pageOf(Card.paged(Lists.List.cons({head: "{who}'s commonplace book.\n", tail: body}), bookRoom(context)), deeds.page, spell)
# A page's room: a reply's characters less the line under it.
def bookRoom(context: Abi.Context) -> Nat:
  Card.room(context) - 160n
def deedsOf(rows: Lists.List<World.Deed>, what: String) -> Lists.List<World.Deed>:
  Lists.filter(rows, fn(d: World.Deed) -> Bool: d.what == what)
def bookLines(state: State, rows: Lists.List<World.Deed>) -> Lists.List<String>:
  let made = deedsOf(rows, "made")
  let heard = Lists.filter(Relations.rows(state.heard), fn(h: Heard) -> Bool: Lists.any(made, fn(d: World.Deed) -> Bool: d.object == h.thing))
  Lists.concat(Lists.concat(Lists.concat(madeLines(made, heard), grownLines(deedsOf(rows, "grew"))), Lists.concat(placedLines(Lists.filter(rows, fn(d: World.Deed) -> Bool: d.what == "rule" || d.what == "want")), visitedLines(deedsOf(rows, "visited")))), wroteLines(Lists.reverse(deedsOf(rows, "wrote"))))
def entryOf(at: Nat) -> String:
  if at == 0n then "" else ", entry {natText(at)}"
# `Made 3; 4 other hands acted on 2 of them:` then each thing, what it is, when, and what others did to it.
def madeLines(made: Lists.List<World.Deed>, heard: Lists.List<Heard>) -> Lists.List<String>:
  let touched = Lists.length(Lists.filter(made, fn(d: World.Deed) -> Bool: Lists.any(heard, fn(h: Heard) -> Bool: h.thing == d.object)))
  let hands = Lists.length(distinct(Lists.map(heard, fn(h: Heard) -> String: h.by)))
  let others = if hands == 1n then "1 other hand" else "{natText(hands)} other hands"
  let head = if hands == 0n then "Made {natText(Lists.length(made))}:\n" else "Made {natText(Lists.length(made))}; {others} acted on {natText(touched)} of them:\n"
  if Lists.length(made) == 0n then Lists.List.nil() else Lists.List.cons({head: head, tail: Lists.map(made, fn(d: World.Deed) -> String: madeLine(d, Lists.filter(heard, fn(h: Heard) -> Bool: h.thing == d.object)))})
def madeLine(d: World.Deed, rows: Lists.List<Heard>) -> String:
  let parts = textJoin(Lists.map(distinct(Lists.map(rows, fn(h: Heard) -> String: h.verb)), fn(verb: String) -> String: verbPart(verb, Lists.filter(rows, fn(h: Heard) -> Bool: h.verb == verb))), "; ")
  let done = if parts == "" then "" else ": {parts}"
  let coined = if d.text == "" then "" else ", coined by {d.text}"
  "  {d.object}, a {d.name}{coined}{entryOf(d.at)}{done}\n"
def grownLines(grew: Lists.List<World.Deed>) -> Lists.List<String>:
  if Lists.length(grew) == 0n then Lists.List.nil() else Lists.List.cons({head: "Grew {natText(Lists.length(grew))}:\n", tail: Lists.map(grew, fn(d: World.Deed) -> String: "  {d.name} on {d.object}{entryOf(d.at)}{reading(d.text)}\n")})
def placedLines(placed: Lists.List<World.Deed>) -> Lists.List<String>:
  if Lists.length(placed) == 0n then Lists.List.nil() else Lists.List.cons({head: "Placed {natText(Lists.length(placed))}:\n", tail: Lists.map(placed, fn(d: World.Deed) -> String: "  {d.what} {d.name} on {d.object}{entryOf(d.at)}{reading(d.text)}\n")})
# A verb's, rule's or want's reading after its name, quoted, when it has one.
def reading(text: String) -> String:
  if text == "" then "" else ": “{Card.cut(text, 80n)}”"
# The lines, newest first, each where it was written and its words cut to 100.
def wroteLines(wrote: Lists.List<World.Deed>) -> Lists.List<String>:
  let count = if Lists.length(wrote) == 1n then "1 line" else "{natText(Lists.length(wrote))} lines"
  if Lists.length(wrote) == 0n then Lists.List.nil() else Lists.List.cons({head: "Wrote {count}, newest first:\n", tail: Lists.map(wrote, fn(d: World.Deed) -> String: "  on {d.object}{entryOf(d.at)}{verbNote(d.name)}: “{Card.cut(firstLine(d.text), 100n)}”\n")})
def verbNote(verb: String) -> String:
  if verb == "" || verb == "write" then "" else ", a {verb}"
def visitedLines(visited: Lists.List<World.Deed>) -> Lists.List<String>:
  let places = textJoin(Lists.map(visited, placeWord), ", ")
  if Lists.length(visited) == 0n then Lists.List.nil() else Lists.List.cons({head: "Visited: {places}.\n", tail: Lists.List.nil()})
def placeWord(d: World.Deed) -> String:
  if d.name == "here" then "{d.object} (here)" else d.object