Skip to content

ADR-020: Campaign Arc System — narrative-driven multi-session play

Status: Accepted Date: 2026-04-09 Context: Players currently play standalone sessions with no continuity between them. We want to add a "D&D campaign" style experience where a group (or solo player) progresses through a narrative arc across multiple sessions, with branching paths, escalating stakes, and inter-session narrative interludes. The existing campaigns table (migration 029) provides a basic grouping of sessions but has no arc structure, chapter progression, narrative bridges, or party voting mechanics.

At a glance

What it decides: Multi-session "campaign" play runs on a four-table arc system: reusable arc templates define 5–7 chapters with narrative phases, and a campaign instance progresses through them with AI-generated interludes weaving outcomes into a story.

  • Structure / content / glue split arcs supply pacing and phase constraints; the existing 500+ scenario library supplies content via filtered selection; AI interludes supply the narrative bridge between chapters.
  • Sync-weighted path voting at fork points, each member's vote is weighted by their just-completed sync score — high sync buys more influence over the story.
  • Dynamic assembly over authored trees pre-authored branching was rejected as combinatorially unscalable; replay-after-months is treated as fresh because it measures consistency.
  • Solo-first, multiplayer layers on solo needs no party mechanics; an async fallback (48h default deadline) keeps group campaigns alive when the party can't meet.
  • Watch: experience quality hinges on interlude-generation prompt quality (Phase 3).
flowchart LR
    A["Arc template (5–7 chapters)"] --> B["Chapter (phase + filters)"]
    B --> C["Scenario selected from library"]
    C --> D["Party plays + sync scores"]
    D --> E{"Fork point?"}
    E -->|"yes"| F["Sync-weighted path vote"]
    E -->|"no"| G["AI interlude"]
    F --> G
    G --> B

Per-chapter loop: arc structure constrains selection, play produces sync scores, forks route the next chapter, interludes bridge them.

Decision

Introduce a Campaign Arc System with four new tables layered on the existing campaigns table:

1. campaign_arcs — Arc templates

A reusable narrative arc template that defines the structure of a campaign. Each arc has a theme, a chapter count (5-7 recommended), and a narrative voice prompt that guides AI-generated interludes.

2. campaign_chapters — Chapters within an arc

Each chapter defines a position in the arc (1-N), a narrative arc phase (setup / rising / crisis / climax / resolution), and scenario selection constraints (theme, category filters). Chapters may define fork points — moments where the party's choice determines which chapter comes next (via fork_options JSONB).

3. campaign_progress — Campaign instance state

Tracks a specific campaign's progress through an arc: current chapter, accumulated party cohesion score, narrative history (JSONB array of interlude texts and outcome summaries), and campaign status (active / paused / completed / abandoned).

4. campaign_members — Party membership

Links players to campaigns with role (creator / member), join date, and per-member stats (games played in this campaign, personal cohesion contribution).

Scenario Selection: Recency-Aware Freshness

The scenario scheduler gains a campaign-aware recency filter: - Scenarios played in the current campaign are excluded (no repeats within an arc). - Scenarios played by any party member in the last 30 days outside this campaign get a heavy penalty (0.1 freshness). - Scenarios played 30-90 days ago get a mild penalty (0.5 freshness). - Scenarios played 90+ days ago are treated as fully fresh (1.0 freshness) — replaying after months tests consistency, which is valuable.

Party Path Voting: Sync-Weighted

At fork points, each party member votes on which path to take. Votes are weighted by that member's sync score from the just-completed session: - weighted_vote = vote × (sync_score / 100) - Highest weighted total wins. - Ties broken by party creator (the "navigator").

This makes the sync game consequential beyond score — high sync = more influence over the story.

Narrative Interludes

Between chapters, the system generates a narrative bridge using: - The arc's narrative voice prompt - The scenario just played and its outcome (which options the party chose, sync scores) - The fork choice (if any) - Campaign history so far

Interludes are stored in campaign_progress.narrative_history as a JSONB array, building a readable story log.

Solo vs. Multiplayer

  • Solo campaigns: campaign_members has one entry. No voting needed — the solo player picks the path at forks. Narrative voice addresses "you."
  • Live multiplayer campaigns: Full party, real-time play, sync-weighted voting.
  • Async fallback: If the party can't meet, individual members can play their chapter solo. Their choices and sync scores are recorded. Path voting happens async with a configurable deadline (default 48h). This preserves momentum without losing the group narrative.

Campaign Length

Arcs define 5-7 chapters by default. The arc phase progression ensures narrative pacing: - Chapter 1: Setup — low stakes, establish the world - Chapters 2-3: Rising — stakes increase, relationships form - Chapter 4-5: Crisis — hard choices, high consequences - Chapter 6: Climax — the defining moment - Chapter 7 (optional): Resolution — aftermath, reflection

Escalating Sync Mechanics

Campaign chapters can modify the sync game experience: - trust_bonus: In later chapters, players who synced well previously see each other's initial leanings earlier in the prediction phase. - stakes_multiplier: Sync score changes are amplified in crisis/climax chapters (1.5x-2x). - cohesion_weight: How much this chapter's sync contributes to the campaign's overall party cohesion score.

Rationale

  • Dynamic scenario assembly over authored branching trees: the existing scenario library (500+ scenarios across 5 themes) is large enough to support filtered selection per chapter. This avoids the combinatorial explosion of pre-authored paths while still delivering narrative coherence through the interlude system and arc phase constraints.
  • Sync-weighted voting ties the core mechanic to narrative consequence, creating a feedback loop: play well → influence the story → care more about playing well.
  • Solo-first MVP reduces coordination overhead. A solo campaign is a curated sequence with narrative bridges — no party mechanics needed. Multiplayer layers on top without architectural changes.
  • Recency-aware freshness addresses the concern about scenario repetition without being overly strict — replaying a scenario after months is actually valuable for measuring consistency growth.

Alternatives Considered

  • Fully authored branching trees: More narratively coherent but requires authoring N×M scenarios per campaign. Doesn't scale. Could revisit for "premium" hand-crafted campaigns later.
  • Majority vote (unweighted): Simpler but doesn't connect the sync mechanic to narrative outcomes. Misses the key design insight.
  • Fixed chapter count: Considered always-5 or always-7. Flexible range is better — a "crisis sprint" arc of 3 chapters or an epic of 7 serve different purposes.
  • Real-time only multiplayer: Would kill campaigns the same way it kills D&D groups. The async fallback is essential for retention.

Discussion

The core tension is between narrative coherence (which favors authored content) and replayability (which favors dynamic assembly). We resolved this by splitting the concern: arcs provide the structure (pacing, phase, stakes), scenario selection provides the content (filtered from the library), and AI narration provides the glue (interludes that weave outcomes into story). This means the quality of the experience depends heavily on the interlude generation prompts — Phase 3 narrative quality work is critical.

The recency filter discussion surfaced an important insight: replaying a scenario isn't inherently bad. After months, a player's choices may differ, revealing growth or confirming consistency. The filter should prevent staleness (same scenario two weeks later) not repetition as a measurement tool.

Sync-weighted voting was debated against rotating navigator. Both have merit — rotating navigator is more "D&D" and simpler. But sync-weighted voting is more Sync Game — it makes the core mechanic load-bearing for the campaign experience. We can always add a "rotating navigator" arc option later.

Consequences

  • The existing campaigns table gains a foreign key to campaign_arcs but remains backwards-compatible (standalone campaigns still work with arc_id = null).
  • The scenario scheduler needs a campaign-aware mode that filters by theme, chapter constraints, and recency.
  • AI interlude generation is a new capability that needs prompt engineering (Phase 3).
  • Party cohesion is a new aggregate metric that may surface in the Hub/profile alongside individual sync scores.
  • The async fallback introduces a "deadline" concept that needs UI for setting and tracking.

Key files: - migrations/053_campaign_arcs.sql — new tables and schema extensions - src/types/database.ts — TypeScript types for campaign arc system - src/lib/game/scenario-scheduler.ts — will need campaign-aware filtering (future PR) - src/lib/ai/scenario-generator.ts — will need interlude generation prompts (Phase 3)