Skip to content

Commit 872f958

Browse files
authored
Merge pull request #69 from Daren9m/claude/noncombat-engine-v2-pr2-challenges
feat(challenges): non-combat engine v2 — six challenge frameworks
2 parents f2834f7 + 18818c1 commit 872f958

20 files changed

Lines changed: 3741 additions & 232 deletions

docs/superpowers/plans/2026-07-16-noncombat-engine-v2-pr2-challenges.md

Lines changed: 1672 additions & 0 deletions
Large diffs are not rendered by default.

src/app/challenges/page.tsx

Lines changed: 338 additions & 27 deletions
Large diffs are not rendered by default.

src/data/noncombat-cast.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// ─── Non-Combat Challenge Cast Pools ──────────────────────────────
2+
// Content library for the social framework in src/lib/challenge-
3+
// frameworks/social.ts. PR 2's counterpart to noncombat-scenarios.ts.
4+
// Personas, wants, secrets, and leverage combine to build a principal
5+
// NPC; complications and interruptions add live pressure to the scene.
6+
//
7+
// Authoring rule: no dice expressions or DC numbers in these strings
8+
// — the engines attach the numbers. Keep prose vivid and table-ready,
9+
// D&D fantasy register, no anachronisms.
10+
11+
export interface Persona { archetype: string; quirk: string; speech: string }
12+
export interface Leverage {
13+
kind: 'coin' | 'flattery' | 'threat' | 'logic' | 'favor' | 'secret-for-secret';
14+
approach: string; // what moves them
15+
counter: string; // what backfires
16+
}
17+
18+
// ─── Personas ───────────────────────────────────────────────────────
19+
export const PERSONAS: Persona[] = [
20+
{ archetype: 'a guild clerk drowning in ledgers', quirk: 'stacks and restacks papers when nervous', speech: 'answers questions with smaller questions' },
21+
{ archetype: 'a retired sell-sword turned innkeep', quirk: 'polishes the same tankard throughout', speech: 'short sentences, long pauses' },
22+
{ archetype: 'a temple acolyte assigned to watch the visitors', quirk: 'touches the holy symbol before every lie', speech: 'quotes scripture that never quite fits' },
23+
{ archetype: 'a caravan master counting losses', quirk: 'taps a tally stick against their palm', speech: 'talks in freight weights and travel days' },
24+
{ archetype: "a minor noble's steward doing the noble's real work", quirk: 'straightens whatever is nearest before speaking', speech: 'formal to the point of stiffness, until provoked' },
25+
{ archetype: 'a midwife who has delivered half the district', quirk: 'sizes up strangers like she is checking for a fever', speech: 'blunt, and unbothered by rank' },
26+
{ archetype: 'a river ferryman who has seen every kind of passenger', quirk: 'spits over the rail before naming a price', speech: 'trades in questions that are really answers' },
27+
{ archetype: 'a disgraced hedge-mage scraping by on charms', quirk: 'mutters the end of every sentence twice', speech: 'over-explains the harmless magic and dodges the rest' },
28+
{ archetype: 'a town crier who knows more than the news he shouts', quirk: 'clears his throat before anything true', speech: 'performs even private conversations' },
29+
{ archetype: 'a mercenary captain between contracts', quirk: 'counts exits in any room within a breath of entering', speech: 'names a price for everything, including advice' },
30+
{ archetype: 'a widowed innfolk running the family trade alone', quirk: 'wipes hands on an apron that is never actually dirty', speech: 'warm until money or the dead come up' },
31+
{ archetype: "a gatehouse sergeant tired of being no one's priority", quirk: 'rolls a shoulder that never quite healed right', speech: 'clipped orders even when making requests' },
32+
{ archetype: 'a traveling relic-appraiser with a magnifying loupe on a chain', quirk: 'squints at anything shiny, mid-sentence', speech: 'prices things aloud without meaning to' },
33+
{ archetype: 'a shrine keeper tending a god whose worship has thinned', quirk: 'lights a candle for every visitor, believer or not', speech: 'gentle, but circles back to the same worry' },
34+
];
35+
36+
// ─── Wants ──────────────────────────────────────────────────────────
37+
export const WANTS: string[] = [
38+
'safe passage for a wagon that must not be inspected',
39+
"a rival's letter retrieved before it is read aloud at council",
40+
'proof that a debt was already paid, before the collectors arrive',
41+
'someone trustworthy to carry a message no courier will touch',
42+
'a missing relative found before the harvest festival',
43+
'a rival merchant undercut without anyone tracing it back',
44+
'an old promise called in, quietly, without witnesses',
45+
'a stolen heirloom returned before its absence is noticed',
46+
'a dangerous rumor about them stopped before it spreads further',
47+
'an outsider to vouch for them at a hearing they cannot attend',
48+
'safe escort past a watch post they would rather not explain',
49+
'a poisoned business deal undone before the ink dries',
50+
'someone willing to take the blame for a mistake that was not theirs',
51+
'a family grudge settled without it turning into a feud',
52+
'a shipment recovered before its owner learns it went missing',
53+
'a confession pried from someone who owes them nothing',
54+
'protection for a family member who has made dangerous enemies',
55+
'a forged document quietly destroyed before it is used against them',
56+
'an old rival humiliated in a way that cannot be traced back to them',
57+
'the truth about a death that the authorities have already closed the book on',
58+
];
59+
60+
// ─── Secrets ────────────────────────────────────────────────────────
61+
export const SECRETS: string[] = [
62+
'the debt they owe is to someone who does not forgive in coin',
63+
'they witnessed the crime everyone is asking about — from the wrong side',
64+
'they are not who their papers say they are',
65+
'they once took a bribe that got someone else killed',
66+
'they are hiding a sibling the law wants for a crime long past',
67+
'the object everyone assumes was stolen was theirs to give away',
68+
'they have been forging a signature for years and no one has noticed',
69+
'they know exactly where the missing person is, and why they left',
70+
'they broke a vow to a temple that has not forgotten',
71+
'the accident that maimed them was no accident at all',
72+
'they are quietly buying up debts to seize the land beneath them',
73+
'they have been feeding information to the very people they claim to fear',
74+
"they buried a body and have told three different lies about the grave since",
75+
'their claim to the family trade rests on a will that was never real',
76+
'they let an innocent take the blame once, and would do it again',
77+
'they are dying of something slow, and no one else knows yet',
78+
'the ledger they keep so carefully is a second, false set of books',
79+
'they once loved someone the party is now hunting',
80+
'they arranged the very disaster they are now offering to help solve',
81+
'they have been quietly stockpiling supplies for a war no one else believes is coming',
82+
];
83+
84+
// ─── Leverage ───────────────────────────────────────────────────────
85+
export const LEVERAGE: Leverage[] = [
86+
{ kind: 'coin', approach: 'a fair price named plainly, half up front', counter: 'haggling insults them — the price rises' },
87+
{ kind: 'secret-for-secret', approach: 'trade a confidence of equal weight', counter: 'a hollow or invented secret ends all trust' },
88+
{ kind: 'flattery', approach: 'genuine praise for the one thing they are actually proud of', counter: 'flattery that misses the mark reads as mockery' },
89+
{ kind: 'threat', approach: 'a quiet, specific warning of a consequence they cannot afford', counter: 'an empty or exaggerated threat makes them call the bluff — and remember the insult' },
90+
{ kind: 'logic', approach: 'lay out the plain arithmetic of their own self-interest', counter: 'condescension in the explanation turns agreement into stubbornness' },
91+
{ kind: 'favor', approach: 'call on a debt they already owe, or offer one worth collecting later', counter: 'a favor that costs them more than it is worth breeds resentment, not gratitude' },
92+
];
93+
94+
// ─── Social Complications ───────────────────────────────────────────
95+
export const SOCIAL_COMPLICATIONS: string[] = [
96+
'a rival faction watches the conversation and will act on whatever is agreed',
97+
'the persona is being watched by someone they are terrified of, and it shows',
98+
'a mutual acquaintance arrives and expects to be included in the conversation',
99+
'the persona has already promised the same thing to someone else',
100+
'guards patrol close enough to overhear anything said too loudly',
101+
'the persona is testing the party to see if they can be trusted with something larger',
102+
'someone in earshot is taking notes for a person the party has not met yet',
103+
'the persona is only half paying attention — a real crisis is unfolding elsewhere',
104+
'the deal being struck would ruin a friend of the persona if it ever got out',
105+
'a child or apprentice of the persona is present and old enough to understand everything',
106+
];
107+
108+
// ─── Interruptions ──────────────────────────────────────────────────
109+
export const INTERRUPTIONS: string[] = [
110+
'a third party arrives mid-conversation with a competing offer',
111+
'a messenger bursts in with news that changes the persona\'s priorities entirely',
112+
'the persona is suddenly and visibly recognized by someone they hoped to avoid',
113+
'a loud commotion outside draws everyone\'s attention for a crucial moment',
114+
'someone the persona owes money to walks in and demands to be paid on the spot',
115+
'a piece of evidence relevant to the secret is dropped, delivered, or overheard in the room',
116+
'the persona receives a note that visibly unsettles them and changes their tone',
117+
'an old enemy of the persona recognizes one of the party and says so, loudly',
118+
];

0 commit comments

Comments
 (0)