Wiring, long form

Wiring the brain into your engine

Everything a developer does to put a mind in a character, in the order they do it. The Characters SDK says what a character is; this page says what your engine sends, what it gets back, and what it must execute. Two hours for a first character, if your engine can already move an actor to a point and make it say a line.

1 What you are connecting to

lw dev starts the brain on the developer's machine. Your engine talks to one socket:

ws://localhost:8765        the broker — JSON messages, one per line, both directions

Your engine sends the state feed on it, several times a second. The brain sends back commands on it: move this character like this, say this. That is the whole wire. Everything else (perceptions, orders, receipts) is HTTP on lw serve and is optional at first.

If your engine cannot hold a socket (Papyrus cannot), copy the Skyrim adapter's pattern: an HTTP shim on localhost:8766 that takes POST /state and answers with every command queued since the last post. The post is the poll. pipeline/skyrim_bridge.py is that shim, and it is about 500 lines.

2 The state feed — what you send

One message per tick, 3 to 10 Hz. Positions are in your engine's units on a horizontal plane (x, z); the brain never reads your navmesh.

{
  "type": "state",
  "tab": "my-game",                       // one id per running game instance
  "world": "my-game",                     // MUST equal the manifest's "world"
  "player": { "x": 0.0, "z": 0.0, "armed": false },
  "npcs": [
    { "i": 0, "name": "Hulda", "x": 8.0, "z": 4.0, "handle": 1000,
      "armed": false, "activeExpert": "idle",
      "identity": { "klass": "CitizenInnkeeper", "city": "Whiterun" },   // optional: seeds an archetype
      "location": "The Bannered Mare", "loctype": "inn", "interior": true  // optional: place tags (§02)
    }
  ],
  "objects": [ { "label": "well", "x": 3.0, "z": -2.0, "kind": "furniture" } ]   // optional: things a plan can go to
}
fieldrequiredwhat the brain does with it
npcs[].iyesthe slot index commands come back on; stable while the character is present
npcs[].nameyesthe character's identity: journal, manifest characters, receipts. Nameless crowd rows may have name: null
npcs[].x, zyesdistances: to the player, to the guard, to allies, to stations
npcs[].handlerecommendedyour engine's stable id, so renames and slot shuffles never lose a mind; commands carry it back
npcs[].armedrecommendedthe threat rung: a drawn weapon near a character outranks everything
npcs[].activeExpertrecommendedwhat the body is doing now (idle, or the last command's expert); lets the brain not repeat itself
npcs[].identity.klass / .guardoptionalseeds the archetype (guard, owner, elder, child, drifter) when no persona is authored
npcs[].location, loctype, interioroptionalthe place tags never rules use (place.temple, interior)
player.armedyesthe same threat rung, from the player's side
objects[]optionallabelled things: a plan step can navigate to them; the talk lane can refer to them

The brain keeps nothing you did not send. If a field is missing, the rule that needs it evaluates to false and the character simply does not consider it.

3 Commands — what you get back, and must execute

Every command carries npc (the slot) and name; when you sent a handle it carries that too. There are two you cannot skip.

loadScenario — a body movement. modes[].expert is one of the engine actions in the vocabulary (the table in §⁠5). Execute it against the target, keep executing it until the next loadScenario for that slot, and report it back as activeExpert.

{ "cmd": "loadScenario", "npc": 0, "name": "Hulda", "handle": 1000, "title": "plan:report",
  "target": "player",
  "modes": [ { "cond": "default", "expert": "navigate-to", "target": "guard" } ] }

target is "player", another character's name, or absent when modes[].focus gives a point [x, y, z].

say — a line, aimed at someone.

{ "cmd": "say", "npc": 0, "name": "Hulda", "text": "Guard. I've been robbed.", "title": "plan:report" }

Render it however your game renders speech. It is already the character's decision; do not filter it.

Commands you may ignore at first: castRole, setSituation, setGoal, toast, buildScene. They belong to scenes and the Architect (Characters SDK §⁠12) and a game that never uses those features never receives them.

4 The manifest, and the load report

Your level ships lw.manifest.json (lw init writes one). Its world must equal the feed's. Each verb names the engine action it maps to, and your adapter declares which engine actions it can execute. At start, ask for the report:

python -m pipeline.verb_manifest --check lw.manifest.json --executes approach,evade,navigate-to,say --adapter my-game

or POST /v1/manifest/check on lw serve. It prints one line: every verb executable here, or CANNOT execute here: sit (sit), lean (lean). A verb your adapter cannot execute is still planned and spoken; your adapter no-ops it. Fix it by binding the engine action, not by editing the brain.

5 The vocabulary — verbs to engine actions

engine actionwhat your engine must doverbs that map to it
approachwalk to within speaking distance of the target and face itgreet, serve, investigate
navigate-towalk to the target or point; stop therereport, go_station, seek_station, buy_from, ask_friend
followstay within a few units of the target as it movesguard_player
guardhold position, face the target, keep eyes on itwatch
evademove away from the target, fastflee
retreatback off a few steps, then holdcall_help, avoid
confrontclose to arm's length, square up, holdconfront
leadwalk ahead of the target toward a point, waiting when it lagslead
sayspeak the line; no movementwarn, complain, demand, refuse_service, gratitude, claim_task, resume_task, say
sit, eat, leanuse the nearest matching furnituresit, eat, lean
wander2roam locallysandbox

A first adapter needs approach, navigate-to, evade, retreat, guard, follow, confront and say. That is enough for every reflex and every shipped plan; the rest are bodies you add when your level lists the verb.

6 Perception — telling a character what happened

The brain does not see your world. You tell a character what it witnessed, in the first person, in your words:

POST http://localhost:8767/v1/perceive   { "char": "Hulda", "text": "the newcomer took a purse of 40 septims right off me" }

lw serve is that endpoint; without it, send {"kind": "perceive", "char": "...", "text": "..."} on the socket. The gate classes the line (theft, attack, threat, slight, debt, gift, amends, need, or ambient), stamps its weight, and the deliberator decides whether it is worth planning about. Your engine's hooks are the senses: on-hit, on-steal, on-gift, on-insult, each one line. Phrasing does not matter; meaning does. Level-specific phrasings you want classed a fixed way go in the manifest's events section (Characters SDK §⁠02).

7 Orders, receipts, dialogue

8 Shipping

In a shipped build the local half is the same code, pointed at the hosted deliberator. lw bundle --target windows|mac --build-key <key> packs it as one folder (embedded Python, the embedder, your weights, the manifest, a launcher) that your installer carries and your game starts with lwbrain --parent <pid>; the player installs nothing. The shipped brain wakes with a build key (lw licence buildkey create), never your licence key. Reflexes, perception, memory and running a plan happen on the player's machine; one small request per wake goes to the service. Nothing else leaves the machine. Pull the cable and the town keeps living; it stops forming new long plans until the wire is back. Step by step: Ship with a licence.

9 Reference adapters