Skip to content

Topics

A topic is one subject of talk inside a conversation. It is a small graph of turns. Each turn names a speaker and a move. The first turn opens the topic, and each turn lists the turns that can reply to it.

A topic has no greeting and no goodbye. The encounter adds those around the topics.

data/<namespace>/conversation/<name>.json

The file ID is the topic ID. For example, data/townstead/conversation/weather.json is the topic townstead:weather.

  • Generative topics name moves, and Townstead builds each line from parts. Every topic in data/townstead/conversation/ is generative.
  • Set pieces have fixed text. Each turn lists translation keys in lines, and the villager says one of them. The topics in data/townstead_social/conversation/ are set pieces. They can give outcomes: memories, opinion and mood.

Both kinds live in the same folder and use the same format.

A generative topic about the weather:

{
"schema": "townstead:conversation/v1",
"subjects": ["townstead:weather"],
"register": "ambient",
"start": "t0_open",
"turns": {
"t0_open": {
"speaker": "initiator",
"move": "townstead:open_subject",
"replies": ["t1_agree", "t1_disagree"]
},
"t1_agree": { "speaker": "responder", "move": "townstead:agree", "weight": 3 },
"t1_disagree": { "speaker": "responder", "move": "townstead:disagree" }
},
"associated": ["townstead:biome"],
"expansions": {
"moves": ["townstead:elaborate", "townstead:opinion_ask", "townstead:anecdote"],
"max": 2
}
}

A set piece:

{
"schema": "townstead:conversation/v1",
"context": ["weather:rain"],
"start": "open",
"turns": {
"open": {
"speaker": "initiator",
"move": "townstead:say",
"lines": ["dialogue.examplepack.rain.open.1", "dialogue.examplepack.rain.open.2"],
"duration": "4s",
"replies": ["reply"]
},
"reply": {
"speaker": "responder",
"move": "townstead:say",
"lines": ["dialogue.examplepack.rain.reply.1"],
"outcome": {
"memory": "townstead_social:pleasant_exchange",
"initiator_opinion": 1,
"responder_opinion": 1
}
}
}
}
FieldRequiredDescription
schemaYesMust be townstead:conversation/v1.
turnsYesA map from turn ID to turn. 1 to 32 turns.
startNoThe ID of the first turn. The default is the first turn in turns.
subjectsNoThe subjects the topic can be about: subject IDs, or # subject tags. With no subjects, the topic has no subject.
registerNoThe kind of talk, such as ambient, social, humor, grief, civic, commerce or intrigue. The default is ambient. A voice uses the register to set how often its own phrasing appears. See registers.
weightNoHow likely the topic is, compared with others. More than 0 and at most 100. The default is 1.
associatedNoSubject IDs. When the previous topic had one of these subjects, the state key assoc is true, so lines like “Speaking of which.” can join the topics.
expansionsNoExtra moves after the last turn. See Expansions.
requiresNoRequirements that must all be true for the topic to start.
Gate fieldsNocontext, relationship, personality_weights, relationship_weights, when and evaluation. See Gates.
interestsNoAccepted, but not used yet.
content_tagsNoAccepted on topics, but not used yet. Use content_tags on lines instead.

A topic file does not accept a mods field.

FieldRequiredDescription
speakerYesinitiator for the villager who started the topic, or responder for the other villager.
moveYesThe move for this turn, such as townstead:agree.
repliesNoTurn IDs that can come next. A turn with no replies ends the topic.
weightNoHow likely this reply is, compared with the other replies. More than 0 and at most 100. The default is 1.
linesNoFor a set piece: up to 16 translation keys. The villager says one of them instead of a built line.
durationNoHow long the line shows, as ticks (80) or seconds ("4s"). 20 to 200 ticks. By default, Townstead sets the time from the length of the text.
registerNoA register for this turn only. It replaces the topic’s register.
gestureNoWhat the speaker does: {"performance": "<clip>", "expression": "<cue>"}. It replaces the move’s gesture.
listenerNoWhat the other villager does while listening. Same format as gesture.
outcomeNoWhat the topic changes when it ends on this turn. Only a turn with no replies can have one. See Outcomes.
content_tagsNoAccepted, but not used yet.
Gate fieldsNoSee Gates. A reply with a gate weight of 0 cannot be chosen.
  • Every turn must be reachable from start.
  • The graph has no cycles. A path from start has at most 8 turns.
  • Before a turn with an outcome, both villagers must speak at least once on the path.

When a turn has more than one reply, Townstead weighs them. The weight of a reply is its weight times its gate weight for the villager who speaks it. Townstead tries the replies in a weighted random order. If it cannot build a line for a reply, it tries the next one.

For example, the news topic townstead:event has the replies ask_detail, doubt, react, respond and console. The respond pools are for news about the listener (“I heard you made master!” “It took forever, but I did it!”). The react pools are for news about someone else. For each piece of news, Townstead can build only the replies whose lines fit, so it uses those.

After the last turn, a topic can go on for a few more lines. Expansions are moves such as townstead:elaborate (more about the same subject), townstead:opinion_ask (ask what the other villager thinks) and townstead:anecdote (a short story).

FieldDescription
movesMove IDs that can follow the topic.
maxThe most expansions for this topic, from 0 to 6. The default is 2 when expansions is present, and 0 without it.

The encounter also has a maximum. Townstead uses the smaller of the two. Before each expansion, Townstead checks the interest of the less interested villager. The lower the interest, the more likely the topic stops. A move can have a follow_up move that always comes next, and a move can be initiator_only. See Moves.

Topics, turns, subjects and lines share the same gate fields. A gate gives a weight. A weight of 0 blocks the choice.

FieldDescription
contextContext facts that must all be true. See Context Facts.
relationshipRelationship tags that must all be true, such as friend, familiar, stranger, strained, partner or family.
personality_weightsA map from MCA personality to weight. Townstead uses the weight of the villager’s personality, or default when it is not listed. The default of default is 1.
relationship_weightsA map from relationship tag to a multiplier. Townstead multiplies every tag that matches. When no tag matches, it uses default.
whenA Pheno condition. The gate is 0 when the condition is false.
evaluationA Pheno value. Townstead multiplies the weight by it, up to 100. A value of 0 or less blocks the choice.

Weights in personality_weights and relationship_weights must be more than 0 and at most 100. Personality keys can use the bare MCA name (grumpy) or the mca: form.

An outcome is what a topic changes when it ends. It is for set pieces. None of the generative topics that Townstead ships have one.

"outcome": {
"memory": "townstead_social:pleasant_exchange",
"initiator_opinion": 2,
"responder_opinion": 2,
"initiator_mood": 1,
"responder_mood": 1,
"initiator_relationship": [{ "quality": "townstead:comfort", "amount": 1 }],
"responder_relationship": [{ "quality": "townstead:comfort", "amount": 1 }]
}
FieldRequiredDescription
memoryYesThe memory ID that both villagers keep.
initiator_memory, responder_memoryNoA different memory for one villager. The default is memory.
initiator_opinion, responder_opinionNoA change to affection toward the other villager, from -4 to 4.
initiator_mood, responder_moodNoA change to MCA mood, from -4 to 4.
initiator_relationship, responder_relationshipNoA list of changes to relationship qualities. Each has quality (an ID such as townstead:trust), amount (not 0, from -100 to 100) and half_life_days (0 or more; leave it out to use the quality’s own half-life). A quality can appear only once in a list.
spreadNoAccepted, but not used yet.

Townstead limits how often outcomes apply. One pair of villagers gets at most 2 outcomes a day, and one villager gets at most 4 a day. After that, conversations still happen, but they change nothing.

Townstead ships these relationship qualities: townstead:affection, townstead:attraction, townstead:comfort, townstead:familiarity, townstead:fear, townstead:resentment, townstead:respect and townstead:trust.

A register is a free-form name for the kind of talk. Townstead uses it in two places:

  • A voice sets how often its phrasing appears in each register.
  • A line can require a register with registers, or keep out of one with "registers": "!grief".

The line takes its register from, in order: the turn’s register, the subject’s register, then the topic’s register.

RegisterUsed by
ambientWeather, biome, needs.
socialNews, mob sightings, company, venues.
humorJokes.
griefZombie infection talk.
civicVillage projects.
commerceQuest boards.
intrigueRumours about rulers.

Culture voices also use grief, family, romance, work, advancement and other names. You can add new registers. They only need to match between your topics, subjects, lines and voices.

When a villager starts a new topic, Townstead looks at every topic that has not come up yet in this conversation. A topic can start when:

  1. its requires are all true,
  2. its gate and the gate of its first turn are more than 0, and
  3. it has no subjects, or at least one subject has a live instance for this speaker and listener.

Townstead then picks one at random by weight. The weight is:

  • the topic weight times its gate weight,
  • times a personal taste from 0.65 to 1.65, fixed for each villager and topic,
  • divided by 1 + 3 × repeats, where repeats counts how often this pair already had the topic,
  • times max(0.1, 1 + 3 × urgency) of the subject. See Goals.

Humor topics are much less likely after a grief topic in the same conversation (× 0.1), and when the speaker has a fever (× 0.2).