Skip to content

feat: NPC & Companion System (spec #28) #29

@Fortinbra

Description

@Fortinbra

Feature Spec: NPC & Companion System

Field Value
Feature NPC & Companion System
Issue #TBD
Status Draft
Author The Doctor
Date 2026-04-10

Overview

What it does

Adds persistent NPCs with names, roles, personality snippets, and relationships to players. NPCs can become companions that join a party during sessions and appear in combat. The Human DM manages NPCs via the management UI, and Ollama receives NPC context during narrative generation.

Why it's needed

NPCs are the backbone of narrative continuity. Persisted NPCs enable quests, world-building, and companions that evolve across sessions.

Out of scope

  • Full merchant economy or shop UI
  • Romance/affinity branching narratives beyond a simple relationship score
  • AI-generated NPC creation (future spec)

Architecture Notes (The Doctor)

Projects / layers touched

  • DungeonMaster.Core — NPC entities + services
  • DungeonMaster.Infrastructure — EF Core mappings + migrations
  • DungeonMaster.Api — NPC endpoints
  • DungeonMaster.Web — NPC management UI
  • DungeonMaster.Bot — NPC interaction commands
  • DungeonMaster.Shared — NPC DTOs

New interfaces in DungeonMaster.Core

  • INpcService — CRUD for NPCs
  • ICompanionService — add/remove companions
  • INpcRelationshipService — relationship tracking per player

Integration points with existing systems


Domain Model

Entities

Npc

  • Name (string)
  • Description (string, markdown)
  • FactionId (Guid?, optional)
  • Role (NpcRole enum)
  • PersonalityPrompt (string)

NpcRelationship

  • NpcId (Guid FK)
  • PlayerCharacterId (Guid FK)
  • Disposition (Disposition enum)
  • AffinityScore (int, 0–100)

Companion

  • NpcId (Guid FK)
  • CampaignId (Guid FK)
  • JoinedAt (DateTimeOffset)
  • LeftAt (DateTimeOffset?)

Enums

NpcRole

  • Shopkeeper
  • QuestGiver
  • Companion
  • Villain
  • Citizen

Disposition

  • Friendly
  • Neutral
  • Hostile

Service Interfaces (CQRS)

public interface INpcService
{
    Task<IReadOnlyList<Npc>> GetAllAsync(CancellationToken ct = default);
    Task<Npc?> GetByIdAsync(Guid id, CancellationToken ct = default);
    Task<Npc> CreateAsync(Npc npc, CancellationToken ct = default);
    Task<Npc> UpdateAsync(Npc npc, CancellationToken ct = default);
    Task DeleteAsync(Guid id, CancellationToken ct = default);
}

public interface ICompanionService
{
    Task AddCompanionAsync(Guid campaignId, Guid npcId, CancellationToken ct = default);
    Task RemoveCompanionAsync(Guid campaignId, Guid npcId, CancellationToken ct = default);
}

API Contract (Rory)

Endpoints

  • GET /api/npcs
  • POST /api/npcs — DM-only
  • PUT /api/npcs/{id} — DM-only
  • DELETE /api/npcs/{id} — DM-only
  • GET /api/campaigns/{id}/companions
  • POST /api/campaigns/{id}/companions — DM-only
  • DELETE /api/campaigns/{id}/companions/{npcId} — DM-only

UI Specification (Clara)

Page / component breakdown

  • NpcRegistry (/npcs) — list, search, and edit NPCs
  • CompanionRoster (/campaigns/{id}/companions) — active companions

Blazor component list

Component File Purpose
NpcRegistry Pages/Npcs/NpcRegistry.razor / .razor.cs NPC list and CRUD
NpcDetail Pages/Npcs/NpcDetail.razor / .razor.cs NPC detail/edit view
CompanionRoster Components/Npcs/CompanionRoster.razor / .razor.cs Displays active companions
RelationshipTable Components/Npcs/RelationshipTable.razor / .razor.cs Player disposition list

Theme / styling requirements

  • Use var(--color-surface) for cards
  • Use var(--color-primary) for friendly disposition badges
  • Use var(--color-danger) for hostile disposition badges

Discord Commands (Rory — Bot Layer)

  • /npc talk {name} — initiates NPC dialogue via narrative engine
  • /npc recruit {name} — DM-only; adds companion to campaign
  • /npc dismiss {name} — DM-only; removes companion

NLP / AI Behaviour (Missy)

Trigger

NPC dialogue requests from /npc talk or narrative contexts that include NPCs.

Context sent to Ollama

  • NPC name, role, and personality prompt
  • Relationship disposition and affinity score
  • Recent conversation history (last N turns)

Expected behaviour

  • NPC stays in character based on personality prompt
  • Must not contradict the Human DM’s overrides
  • Max length: 1–3 paragraphs

Test Scenarios (Danny) ⚠️ COMPLETE BEFORE IMPLEMENTATION

Happy path

  1. Given a DM creates an NPC
    When GET /api/npcs is called
    Then the NPC appears in the list with correct fields

  2. Given a DM recruits a companion
    When POST /companions runs
    Then the NPC appears in the companion roster

Edge cases

  1. Given affinity drops below 0
    When relationship updates
    Then disposition becomes Hostile

  2. Given an NPC is marked as Companion role
    When added to combat
    Then CombatantType is Companion

Error / failure cases

  1. Given a non-DM attempts NPC creation
    When POST /npcs is called
    Then the API returns 403 Forbidden

  2. Given a missing NPC ID
    When /npc talk executes
    Then the bot returns an error message


Acceptance Criteria

Functional

  • NPCs are persisted with name, role, description, and personality prompt
  • Companions can be added and removed from campaigns
  • NPC dialogue uses narrative engine with relationship context
  • DM UI supports CRUD and relationship review

Non-functional

  • API responses under 300 ms for NPC queries
  • All UI components use code-behind pattern
  • CSS uses custom properties only

Dependencies


Agent Work Breakdown

Agent Task Depends On
The Doctor Approve spec
Danny Write failing tests from Test Scenarios Spec approved
Rory Implement NPC entities + API endpoints Danny's tests
Clara Implement NPC UI Rory's API
Danny Confirm all tests pass All implementation

Definition of Done

  • Test Scenarios section completed and approved by The Doctor before implementation
  • All failing tests written by Danny before implementation (TDD)
  • All tests written and passing (xUnit + bUnit)
  • Code reviewed and approved by The Doctor
  • EF Core migration created for NPC tables
  • GitHub issue closed and linked to merged PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestspecSpecification work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions