Adapters — plugging LivingSim into your game
LivingSim is a memory / consequence layer, not a behaviour, dialogue, movement, or generative system. It plugs into the systems you already use and stays the source of simulation truth — it never replaces them, and it never executes actions on their behalf. This page defines the first adapter contracts: the small, read-only query shapes your existing workflows call.
New to the SDK? Start at getting-started.md; this page is
the integration layer on top of it.
The standalone-component boundary
LivingSim ships read-only queries over the LivingSimSdk
facade. An adapter is a thin layer you write in your host that composes
those queries into the shape your decision / dialogue / movement / generative
system wants. The adapter depends on your systems; LivingSim depends on none of
them — so the core stays a standalone component with no behaviour-tree,
dialogue-tool, movement, or LLM dependency.
your systems adapter (host-owned, thin) LivingSim
──────────── ────────────────────────── ─────────────────
behaviour tree ──► BehaviourAdapter ──► LivingSimSdk
dialogue tool ──► DialogueFacts ──► reads ──► (Actions.TryRecord,
movement system ──► movement switch ──► Characters.GetMemory,
LLM / templates ──► context payload ──► Relationships.Get / Explain,
Characters.GetNextIntent,
Time.Advance, Save/Load)
Two contract-wide rules the wording on every adapter keeps:
Characters.GetNextIntentis an inclination / projection, not an executed action. It tells you what an NPC leans toward; your system decides whether and how to act.Relationships.Explainis ranked remembered evidence, not exact per-axis causal math. It lists the memories that account for a feeling, not a closed-form decomposition of the numbers.
1. Behaviour / decision adapter — the first implementation path
For a behaviour tree, FSM, GOAP planner, or hand-rolled decision system. It is the
recommended first adapter because every query below is shipped today over
the facade — and the #456 Unity demo
already drives one (Characters.GetNextIntent, the panel's next: line).
Contract — shipped now (read-only over the facade):
| Query | Backed by | Returns |
|---|---|---|
NextIntent(npc) |
Characters.GetNextIntent |
the inclination — Approach / Avoid / Confront / None, a target, a bounded strength |
HasMemoryOf(npc, subject, eventType) |
Characters.GetMemory |
whether the NPC remembers the subject doing that |
RelationshipBelow(npc, subject, axis, threshold) |
Relationships.Get |
a predicate on any of the six axes |
Why(npc, subject) |
Relationships.Explain |
ranked remembered evidence, for a "why" branch |
using System;
using LivingSim.Core; // EventType
using LivingSim.Sdk;
// A thin behaviour adapter you write in YOUR project, over the read-only facade.
// LivingSim ships no such type, so the core never depends on your decision system.
public static class BehaviourAdapter
{
// "What is this NPC inclined to do?" — shipped + demo-visible. A projection, not an action.
public static NextIntentView NextIntent(LivingSimSdk sdk, string npc)
=> sdk.Characters.GetNextIntent(npc);
// "Does the NPC remember the subject doing this?" — over Characters.GetMemory.
public static bool HasMemoryOf(LivingSimSdk sdk, string npc, string subject, EventType type)
{
foreach (var m in sdk.Characters.GetMemory(npc).Memories)
{
if (m.EventType == type && string.Equals(m.ActorId, subject, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
// "Is affinity toward the subject below a threshold?" — over GetRelationship.
// Generalises to any axis: Trust, Respect, Intimacy, Dependency, ConflictHistory.
public static bool AffinityBelow(LivingSimSdk sdk, string npc, string subject, decimal threshold)
{
var rel = sdk.Relationships.Get(npc, subject);
return rel.Found && rel.Affinity < threshold;
}
// "Why does the NPC feel this way?" — ranked remembered evidence, not per-axis causal math.
public static RelationshipExplanationView Why(LivingSimSdk sdk, string npc, string subject)
=> sdk.Relationships.Explain(npc, subject);
}
// In your behaviour tree's condition / selector:
var intent = BehaviourAdapter.NextIntent(sdk, "nora");
if (intent.Kind == NextIntentKind.Avoid && intent.TargetId == "player")
{
// YOUR tree decides to run the "keep distance" branch — LivingSim only told it the inclination.
}
Contract — future (planner-backed; not in this path): HasGoal(npc, goalType)
(over committed goals) and ExplainLastDecision(npc) require the
AutoBehavior / goal-ranking layer, which this read-only path does not enable. They
are listed for completeness and remain a later slice — distinct from the shipped
read-only Characters.GetNextIntent (an inclination) and Relationships.Explain (evidence). The
explainer for the decision a character holds now has shipped as
Characters.ExplainDecision; it reads the committed plan, so it answers once the
AutoBehavior path has run (it runs during sdk.Time.Step() for every autonomous character, the default).
2. Dialogue adapter — contract / example
Expose deterministic dialogue facts, not generated prose. Your dialogue tool writes the lines; LivingSim provides the facts and stays the source of truth — any text the tool produces is UI, never fed back as simulation state.
| Dialogue variable | Backed by |
|---|---|
trust(npc, subject) / affinity(...) |
Relationships.Get |
remembers(npc, action) / knows(npc, …) |
Characters.GetMemory |
currentIntent(npc) |
Characters.GetNextIntent |
willAnswer(npc, topic) |
your host policy over the facts above (e.g. refuse when affinity/trust is low) |
// A deterministic fact-pack for a dialogue tool — facts, not prose.
public sealed record NpcDialogueFacts(
string NpcId,
IReadOnlyList<MemoryEntryView> RecentMemories, // what the NPC recalls
RelationshipView Toward, // trust / affinity / …
NextIntentView Inclination); // current next intent
public static NpcDialogueFacts DialogueFacts(LivingSimSdk sdk, string npc, string subject)
=> new(npc,
sdk.Characters.GetMemory(npc, max: 5).Memories,
sdk.Relationships.Get(npc, subject),
sdk.Characters.GetNextIntent(npc));
3. Movement adapter — contract / example
Characters.GetNextIntent drives host-owned movement decisions. LivingSim has no
coordinates, NavMesh, A*, or GameObject references — pathfinding stays
entirely in your host; the adapter only translates the inclination into your
movement vocabulary (ApproachCharacter / AvoidCharacter / VisitPlace / …).
NextIntentView intent = sdk.Characters.GetNextIntent(npc);
switch (intent.Kind)
{
case NextIntentKind.Approach: /* your pathfinder: ApproachCharacter(intent.TargetId) */ break;
case NextIntentKind.Avoid: /* your pathfinder: AvoidCharacter(intent.TargetId), or refuse */ break;
case NextIntentKind.Confront: /* your pathfinder: approach with hostile behaviour */ break;
case NextIntentKind.None: /* no inclination → your default movement */ break;
}
Pathfinding is explicitly not built here. The existing engine-side movement seam (
GetMovementIntents()/CompleteMovementCommand) is a separate, deeper integration; this adapter is the simple facade-level mapping.
Items: your game owns where things are
LivingSim keeps the social meaning of a possession: who owns it, what its owner believes about where it is, a theft and who saw it, an owner noticing it gone. It also keeps a logical copy of where each item is, because a theft and a noticed loss depend on it. Your game owns the physical object (its transform, inventory slot, attachment to a hand).
The two stay in step through three rules:
- Your game moved something? Tell LivingSim before the next
Step:sdk.Items.Move(itemId, ItemLocation.AtPlace(placeId))orItemLocation.HeldBy(characterId). It records nothing and changes nobody's belief: an owner still thinks it is where they last saw it, until they see it. - A theft? Ask LivingSim first:
sdk.Actions.TrySteal(thiefId, itemId). Only on success, move the object in your game. Moved first, LivingSim would see the thief already holding it. - Loading a save? Load your game, load LivingSim, then call
Items.Movefor anything your game's save placed differently. Your game's physical state wins. The same save and the same synchronization calls give the same future.
Inventories, carrying capacity, durability, crafting, prices and ordinary use of items stay in your game, or in an optional module: they are not LivingSim's.
4. Generative-prose / LLM context adapter — optional contract / example
Emit a deterministic context payload — the same facts as the dialogue adapter, serialised for a generative system. No prompts are stored as state, and LivingSim takes no LLM dependency: it emits typed facts; what you do with them (template, LLM, nothing) is your choice.
var facts = DialogueFacts(sdk, npc, subject);
var context = new
{
npc = facts.NpcId,
feelsToward = new { facts.Toward.Affinity, facts.Toward.Trust },
remembers = facts.RecentMemories.Select(m => new { type = m.EventType.ToString(), by = m.ActorId }),
inclination = facts.Inclination.Kind.ToString(),
};
Rules the generative layer must honour (enforce them in your prompt / policy — not in LivingSim):
- It may rephrase these facts; it must not invent events, feelings, or motives beyond them (forbidden claims).
- Any topic absent from the payload → the NPC doesn't know it (unknown topic), so the generative layer must not assert it.
- LivingSim remains the source of truth; generated prose is never written back as simulation state.
#459 acceptance criteria → where addressed
| Acceptance criterion | Where |
|---|---|
| Define adapter interfaces for behaviour, dialogue, movement, and optional generative-prose contexts | §1–§4 (each is a documented, host-implemented contract over the shipped facade — no new SDK type) |
| Pick one adapter for the first implementation path | §1 behaviour / decision — every query is shipped today over the facade |
| The #456 Unity slice demonstrates at least one adapter-style query | The demo's next: line calls Characters.GetNextIntent — the behaviour adapter's headline query — live in the panel today |
| Document how these adapters preserve LivingSim as a standalone component | "The standalone-component boundary" above — the adapter lives in the host and depends on your systems; LivingSim depends on none of them |
Next practical product slice after this: packaging / README polish for Unity import, not more simulation depth.