Make NPCs a dedicated section with per-NPC stats
Replace the single shared `npc` stat template (+ npc_card_types) with an `npcs` section: each NPC keyed by a stable id, carrying its own name, description, trigger keys, and its OWN stats block. The AI addresses NPCs as npc.<id>.<stat> (id shown in context), which also fixes the old card-id-guessing problem. On adventure creation each NPC auto-creates a story card (name/keys/desc) for lore + in-scene detection, unless a same-name card already exists. All NPCs instantiate up front. - engine: npcs instantiate/apply/render/reference, npc_name/npc_triggers - builder: _visible_npcs matches each NPC's own keys - create_adventure: auto-create story cards from npcs - WorldStateDrawer: render defined NPCs with their own stats + desc tooltip - demo seed: Gwen (health/trust) + Bandit Leader (health/aggression) - tests updated (34 pass); no new migration (npcs lives in stat_schema JSON) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4dac445f90
commit
cf464a48b0
@@ -57,19 +57,16 @@ def _script_memory(adventure: models.Adventure) -> dict:
|
||||
|
||||
|
||||
def _visible_npcs(adventure: models.Adventure, stat_schema: dict) -> dict[str, str]:
|
||||
"""NPC story cards (by schema-configured type) whose keys appear in the
|
||||
recent story — the ones "in scene", so only their stats get injected."""
|
||||
"""Defined NPCs whose trigger words appear in the recent story — the ones
|
||||
"in scene", so only their stats get injected. Maps npc id -> display name."""
|
||||
actions = [a for a in adventure.actions if a.text.strip()]
|
||||
recent = SEPARATOR.join(a.text for a in actions[-6:]).lower()
|
||||
types = worldstate.npc_types(stat_schema)
|
||||
visible: dict[str, str] = {}
|
||||
for card in adventure.story_cards:
|
||||
if (card.type or "").lower() not in types:
|
||||
for npc_key, ndef in (stat_schema.get("npcs") or {}).items():
|
||||
if not isinstance(ndef, dict):
|
||||
continue
|
||||
for key in (k.strip().lower() for k in card.keys.split(",")):
|
||||
if key and key in recent:
|
||||
visible[str(card.id)] = card.name or f"NPC {card.id}"
|
||||
break
|
||||
if any(trigger in recent for trigger in worldstate.npc_triggers(ndef, npc_key)):
|
||||
visible[npc_key] = worldstate.npc_name(ndef, npc_key)
|
||||
return visible
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user