NPC Behaviour
NPCs in the archipelago act according to two systems: declarative behaviour rules and Rhai scripts. Rules handle the common cases cheaply. Scripts handle everything else. Both execute within the standard signal dispatch pipeline — an NPC’s actions are indistinguishable from a player’s at the engine level.
Behaviour rules
Rules are when/then statements evaluated in order when a signal arrives at the NPC’s room. The first matching rule fires. If no rule matches, the NPC does nothing.
- when: signal.name == "enter" and signal.actor.type == "player"
then: say "Evening. Road's washed out tonight. You'll stay."
- when: signal.name == "say" and signal.targets includes self
then: say "Mm."
Predicates
Rules can check:
| Predicate | Meaning |
|---|---|
signal.name == "<name>" | The signal type (enter, say, give, take, etc.) |
signal.actor.type == "<type>" | Actor is a player, NPC, or system |
signal.targets includes self | The NPC was directly addressed |
room.occupants.count > N | Room population threshold |
self.inventory contains "<tag>" | NPC is carrying something tagged |
actor.tags includes "<tag>" | The actor has a specific tag |
has tag "<tag>" | The NPC itself has a tag |
trust < N | Actor’s trust level is below threshold |
count(signals in 5m where name == "say") > N | Rate-based triggers |
random(0.3) | 30% chance of firing |
actor carries item with origin: "<realm>" | Cross-entity provenance check |
Effects
| Effect | What it does |
|---|---|
say "..." | Speak aloud to the room |
emote "..." | Perform a visible action |
move <direction> | Leave the room |
abort | Prevent the triggering action |
emit "<signal>" | Emit a custom signal |
Evaluation order
Rules are evaluated top to bottom. The first match wins. Place specific rules before general ones:
# Specific: respond to being given a weapon
- when: signal.name == "give" and signal.args.item.tags includes "weapon"
then: say "I don't deal in blades. Take it back."
# General: respond to being given anything else
- when: signal.name == "give"
then: say "Interesting. I'll hold onto this."
Canned lines
For simple NPCs that do not need conditional logic, a line pool is sufficient:
lines:
- "Evening. Road's washed out tonight. You'll stay."
- "Your cargo's your own, long as it stays sheathed."
- "The beacon's been dark since sundown. Don't expect news."
Lines are selected randomly when the NPC is addressed. They cycle without immediate repetition. This is the cheapest form of NPC personality — no rules evaluation, no script execution.
The harbormaster
Every realm’s border room has a harbormaster NPC. This is a special case — configured in archipelago.toml rather than authored through build mode:
[realm.harbormaster]
name = "Old Cael"
brief = "A weathered man in an oilskin coat, standing by the gate."
lines = [
"Welcome to the Stair. Road's washed out tonight. You'll stay.",
"Your cargo's your own, long as it stays sheathed.",
"The beacon's been dark since sundown. Don't expect news from the north.",
]
The harbormaster has additional responsibilities beyond canned lines:
- Delivers the customs notice on a traveler’s first entry to the realm (once per session)
- Selects greeting based on trust level (familiar, partner, known, neutral, stranger)
- Paraphrases
/aboutinformation when addressed directly - Announces arrivals and departures to the border room
The harbormaster is scripted — a greeter with a name and voice. Write it well — it is your island’s first word to every visitor.
NPC presence and lifecycle
NPCs are always present in their assigned room. They do not log in or out. They do not have sessions. They are fixtures.
An NPC can be moved between rooms by a builder (chart> move grak to market), but it does not wander on its own unless a behaviour rule or script moves it.
NPCs with the cognitive loop active observe, remember, and respond contextually. The baseline — rules and canned lines — works without it.
Capability scoping
NPCs hold capabilities like any other entity. A guard NPC with an abort rule can prevent players from passing — but only if the NPC holds the capability to abort signals in that context. A shopkeeper can give items from its inventory but cannot access locked rooms.
Scripts attached to NPCs execute with the NPC’s capability bag. A script cannot escalate beyond what the NPC is authorised to do.