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:
parththakkar106
2026-07-21 15:17:47 +05:30
co-authored by Claude Opus 4.8
parent 4dac445f90
commit cf464a48b0
10 changed files with 206 additions and 106 deletions
+6 -5
View File
@@ -195,10 +195,11 @@ export default function ScenarioEditor() {
<h2 style={{ margin: 0, fontFamily: 'Georgia, serif', fontSize: '1.2rem' }}>World State (RPG)</h2>
</div>
<p className="dim" style={{ margin: '0 0 10px', fontSize: '0.85rem' }}>
Optional. Define stats (with bands and rules) and milestones as a JSON object,
and the AI will track them each turn — HP, mana, an NPC’s trust, quest objectives.
Leave blank for a plain narrative scenario. NPC stats apply to story cards of the
configured <code>npc_card_types</code>.
Optional. Define stats (with bands and rules), NPCs, flags, and milestones as a
JSON object, and the AI will track them each turn — HP, mana, an NPC’s trust,
quest objectives. Each NPC in <code>npcs</code> has its own <code>name</code>,
<code>desc</code>, trigger <code>keys</code>, and <code>stats</code>; a story card
is created for it automatically. Leave blank for a plain narrative scenario.
</p>
<textarea
className="schema-editor"
@@ -206,7 +207,7 @@ export default function ScenarioEditor() {
onChange={(e) => setSchema(e.target.value)}
rows={12}
spellCheck={false}
placeholder={'{\n "player": { "hp": { "min": 0, "max": 100, "initial": 100 } },\n "milestones": { "goal": { "desc": "..." } }\n}'}
placeholder={'{\n "player": { "hp": { "min": 0, "max": 100, "initial": 100 } },\n "npcs": {\n "gwen": { "name": "Gwen", "keys": "Gwen, ranger", "desc": "...",\n "stats": { "trust": { "min": -100, "max": 100, "initial": 20 } } }\n },\n "flags": { "has_key": { "desc": "...", "initial": false } },\n "milestones": { "goal": { "desc": "..." } }\n}'}
/>
{schemaError && <div className="schema-error">⚠ {schemaError}</div>}