← All demos
The indie studioBudget: A small studio’s sprintSix people, a year

The Rusty Anchor

Marta keeps her drowned husband's anchor over the bar. Finn is the only man who came back from that ship. Pip is a bard who has noticed more than she says. Walk in, talk to them, spill a drink, and watch each of them answer the same moment from a different history.

Loading The Rusty Anchor…
Click the game to focus it, then use the controls below.
Click Look around (drag to look if the pointer will not lock)WASD WalkE Talk to the person you are facing
Make something happen in the world
Do something as the player

What this game actually sends

  1. PersonaHow they talk. Authored once.
  2. BackstoryWho they are because of what happened. Authored once.
  3. SituationWhat is going on around them right now. Sent by the game.
  4. EventThe thing that just happened. This is what they react to.
import { npc } from '@npc/client';

// Three characters, one key. Backstories live on the server.
const marta = npc({ key: 'pk_live_…', id: 'marta' });
const finn  = npc({ key: 'pk_live_…', id: 'finn' });

// Each one gets its own view of the same room.
marta.setSituation('Night. A storm is rolling in. You are wiping the bar. A stranger is at the bar.');
finn.setSituation('Night. You are slumped over your mug at the corner table. Marta is behind the bar.');

// Thunder hits, and every character answers it in their own way.
const [m, f] = await Promise.all([
  marta.react('A crack of thunder rattles the shutters.'),
  finn.react('A crack of thunder shakes the whole tavern.'),
]);
playAnimation(marta, m.behaviour); // e.g. 'go_quiet'
playAnimation(finn,  f.behaviour); // e.g. 'flinch'

// Free text goes through say(); actions are checked against a policy.
const r = await marta.say('An ale, please.');
if (r.action?.name === 'serve_drink') pourDrink(r.action.args);

The panel on the right is the real trace of those calls, not a mock-up. Characters here are cast from 8 demo NPCs across three games. Read the docs →