"""Static content: seed words, debate frames, judges, debaters, abilities, word master lists.""" import random from dataclasses import dataclass, field from typing import List, Optional, Tuple # --------------------------------------------------------------------------- # Flavor seeds — injected into word-pool LLM flavor call (hidden from player) # --------------------------------------------------------------------------- FLAVOR_SEEDS = [ "victorian", "deep sea", "carnival", "bureaucratic", "cosmic", "culinary", "medieval", "cyberpunk", "pastoral", "industrial", "noir", "tropical", "arctic", "baroque", "post-apocalyptic", "botanical", "athletic", "diplomatic", "alchemical", "mythological", "nautical", "theatrical", "celestial", "mechanical", "folkloric", "mercantile", "military", "monastic", "nomadic", "futuristic", ] # --------------------------------------------------------------------------- # Seed words (~200 common nouns/concepts — used for topic generation) # --------------------------------------------------------------------------- SEED_WORDS = [ # food & drink "bread", "salt", "sugar", "coffee", "wine", "spice", "feast", "hunger", # technology "algorithm", "screen", "network", "battery", "code", "robot", "signal", "data", # nature "storm", "river", "mountain", "forest", "fire", "stone", "ocean", "seed", # animals "wolf", "crow", "horse", "shark", "bee", "cat", "eagle", "spider", # money & commerce "debt", "market", "price", "wage", "profit", "tax", "coin", "trade", # art & culture "song", "story", "painting", "theater", "dance", "language", "myth", "symbol", # family & society "family", "childhood", "memory", "tradition", "neighbor", "friendship", "marriage", "school", # travel & place "border", "city", "road", "map", "home", "island", "desert", "bridge", # sports & competition "trophy", "team", "race", "victory", "failure", "coach", "training", "rival", # weather & time "sunrise", "shadow", "season", "midnight", "thunder", "clock", "calendar", "silence", # abstract concepts "truth", "freedom", "justice", "power", "chaos", "order", "beauty", "fear", "luck", "patience", "ambition", "pride", "doubt", "courage", "balance", "change", # misc objects "mirror", "key", "gate", "tower", "ladder", "mask", "crown", "flag", "paper", "knife", "hammer", "needle", "rope", "wheel", "bone", "glass", # health & body "sleep", "voice", "hand", "eye", "smile", "pain", "strength", "age", # ideas & knowledge "question", "answer", "secret", "rule", "habit", "experiment", "belief", "theory", # politics & history "empire", "revolution", "vote", "law", "army", "monument", "census", "treaty", # religion & philosophy "soul", "ritual", "paradise", "fate", "virtue", "sin", "prayer", "wisdom", # environment "waste", "resource", "air", "soil", "tide", "flower", "harvest", "plague", ] # --------------------------------------------------------------------------- # Debate frames — thematic lenses injected into topic prompt # --------------------------------------------------------------------------- DEBATE_FRAMES = [ "moral obligation and where it ends", "what this reveals about power and who holds it", "whether the conventional wisdom here is actually right", "the unexamined cost we keep paying", "whether this is a virtue or a comfortable trap", "the gap between what we say we value and what we reward", "whose interests the dominant view actually serves", "what we're too comfortable assuming", "whether our instinct on this is wrong", "what this costs people who don't have the luxury of ignoring it", "whether prioritizing this makes us better or just feel better", "the version of this worth genuinely defending", ] # --------------------------------------------------------------------------- # Master word lists — POWER + STANDARD + SPICE # Every pool: exactly 3 power + 2 spice + 7 standard = 12 words # Pool structure: 4 nouns, 4 adjectives, 3 verbs, 1 wildcard # --------------------------------------------------------------------------- POWER_NOUNS = [ "legacy", "carnage", "reckoning", "verdict", "ruin", "betrayal", "monument", "threshold", "inheritance", "collapse", "sacrifice", "doctrine", "dominion", "covenant", "abyss", "stigma", ] POWER_ADJECTIVES = [ "tyrannical", "irreversible", "complicit", "sovereign", "hollow", "ruthless", "catastrophic", "condemned", "fractured", "inevitable", "desolate", "unforgiving", "absolute", "corrupted", ] POWER_VERBS = [ "betray", "condemn", "dismantle", "fracture", "exploit", "abandon", "justify", "reckon", "surrender", "undermine", "eradicate", "corrupt", "devour", "shatter", "silence", ] STANDARD_NOUNS = [ "tornado", "scoundrel", "fortress", "phantom", "relic", "cipher", "tyrant", "gamble", "cauldron", "spectacle", "facade", "folklore", "paradox", "menace", "ritual", "swindle", "outlaw", "blunder", "grudge", "ransom", "trophy", "alibi", "rumble", "fever", "legend", "circus", "farce", "puppet", "mascot", "casualty", ] STANDARD_ADJECTIVES = [ "brazen", "cursed", "sinister", "mediocre", "infamous", "pompous", "devious", "feral", "turbulent", "smug", "oblivious", "barren", "calamitous", "volatile", "relentless", "baffling", "savage", "frantic", "ominous", "deranged", "colossal", "reckless", "haunted", "ancient", "petty", ] STANDARD_VERBS = [ "ransack", "sabotage", "hoodwink", "scramble", "lurk", "plunder", "banish", "haunt", "ambush", "topple", "swindle", "unravel", "provoke", "seize", "vanish", "crumble", "rampage", "menace", "demolish", "escape", ] STANDARD_WILDCARDS = [ "reckoning", "spectacle", "folklore", "monument", "threshold", "verdict", "paradox", "facade", "cipher", "ritual", "doctrine", "manifesto", "inheritance", "leverage", "residue", ] SPICE_NOUNS = [ "narcissist", "coward", "hypocrite", "doormat", "sellout", "parasite", "scapegoat", "liability", "ego", "puppet", ] SPICE_ADJECTIVES = [ "pathetic", "delusional", "desperate", "shameless", "toxic", "obsessed", "inferior", "paranoid", "manipulative", "spineless", "needy", "insufferable", "petty", "bitter", "gullible", ] SPICE_VERBS = [ "humiliate", "manipulate", "seduce", "exploit", "gaslight", "grovel", "bully", "betray", "resent", "punish", ] SPICE_WILDCARDS = [ "ego", "grudge", "insecurity", "entitlement", "facade", ] POOL_TARGET = {"noun": 4, "adjective": 4, "verb": 3, "wildcard": 1} STATS = { "EDGE": "Persuasion score multiplier. 5 = +10pts on persuasion, 1 = no bonus.", "PRECISION": "Coherence score multiplier. 5 = +10pts on coherence, 1 = no bonus.", "STYLE": "Judge appeal multiplier. 5 = +10pts on judge_appeal with matched judges, 1 = no bonus.", "RISK": "Score variance. 5 = ±15pt random swing applied after scoring. 1 = no swing.", } def _build_word_entries(words: List[str], category: str, tier: str) -> List[dict]: return [{"word": w, "category": category, "tier": tier} for w in words] ALL_POWER_WORDS = ( _build_word_entries(POWER_NOUNS, "noun", "power") + _build_word_entries(POWER_ADJECTIVES, "adjective", "power") + _build_word_entries(POWER_VERBS, "verb", "power") ) ALL_STANDARD_WORDS = ( _build_word_entries(STANDARD_NOUNS, "noun", "standard") + _build_word_entries(STANDARD_ADJECTIVES, "adjective", "standard") + _build_word_entries(STANDARD_VERBS, "verb", "standard") + _build_word_entries(STANDARD_WILDCARDS, "wildcard", "standard") ) ALL_SPICE_WORDS = ( _build_word_entries(SPICE_NOUNS, "noun", "spice") + _build_word_entries(SPICE_ADJECTIVES, "adjective", "spice") + _build_word_entries(SPICE_VERBS, "verb", "spice") + _build_word_entries(SPICE_WILDCARDS, "wildcard", "spice") ) def _count_by_category(words: List[dict]) -> dict: counts = {k: 0 for k in POOL_TARGET} for w in words: counts[w["category"]] = counts.get(w["category"], 0) + 1 return counts def words_too_similar(a: str, b: str) -> bool: """True when two words share a root (e.g. tyrant / tyrannical, storm / stormy).""" a, b = a.lower().strip(), b.lower().strip() if not a or not b: return False if a == b: return True short, long = (a, b) if len(a) <= len(b) else (b, a) if len(short) >= 4 and short in long: return True prefix = 0 for ca, cb in zip(a, b): if ca != cb: break prefix += 1 min_len = min(len(a), len(b)) if prefix >= 5 and prefix >= min_len - 2: return True return False def is_similar_to_any(word: str, existing) -> bool: return any(words_too_similar(word, other) for other in existing) def _sample_from_pool(pool: List[dict], n: int, category: str, used_set: set) -> List[dict]: available = [ w for w in pool if w["category"] == category and w["word"] not in used_set and not is_similar_to_any(w["word"], used_set) ] if len(available) < n: available = [w for w in pool if w["category"] == category and w["word"] not in used_set] source = available if len(available) >= n else [w for w in pool if w["category"] == category] if not source or n <= 0: return [] return [dict(w) for w in random.sample(source, min(n, len(source)))] def _sample_power_spread(available_power: List[dict], n: int = 3) -> List[dict]: """Pick n power words, preferring spread across noun/adjective/verb.""" if len(available_power) <= n: return [dict(w) for w in available_power] by_cat = {"noun": [], "adjective": [], "verb": []} for w in available_power: if w["category"] in by_cat: by_cat[w["category"]].append(w) picked: List[dict] = [] used_words: set = set() for cat in ("noun", "adjective", "verb"): if len(picked) >= n: break pool = [ w for w in by_cat[cat] if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) ] if pool: choice = random.choice(pool) picked.append(dict(choice)) used_words.add(choice["word"]) remaining = [ w for w in available_power if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) ] while len(picked) < n and remaining: choice = random.choice(remaining) picked.append(dict(choice)) used_words.add(choice["word"]) remaining = [ w for w in remaining if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) ] return picked def _sample_spice_spread(available_spice: List[dict], n: int = 2) -> List[dict]: """Pick n spice words — prefer one noun/adj and one verb/wildcard.""" if len(available_spice) <= n: return [dict(w) for w in available_spice] noun_adj = [w for w in available_spice if w["category"] in ("noun", "adjective")] verb_wild = [w for w in available_spice if w["category"] in ("verb", "wildcard")] picked: List[dict] = [] used_words: set = set() pools = [noun_adj, verb_wild] if random.random() < 0.5 else [verb_wild, noun_adj] for pool in pools: if len(picked) >= n: break candidates = [ w for w in pool if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) ] if candidates: choice = random.choice(candidates) picked.append(dict(choice)) used_words.add(choice["word"]) remaining = [ w for w in available_spice if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) ] while len(picked) < n and remaining: choice = random.choice(remaining) picked.append(dict(choice)) used_words.add(choice["word"]) remaining = [ w for w in remaining if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) ] return picked def sample_to_fill_distribution( available_standard: List[dict], already_sampled: List[dict], target: dict, ) -> List[dict]: """Fill remaining standard slots to match per-category targets after tier picks.""" tier_counts = _count_by_category(already_sampled) used_words = {w["word"] for w in already_sampled} result: List[dict] = [] for cat, total_needed in target.items(): still_needed = total_needed - tier_counts.get(cat, 0) if still_needed > 0: sampled = _sample_from_pool(available_standard, still_needed, cat, used_words) result.extend(sampled) used_words.update(w["word"] for w in sampled) return result def _repair_similar_words(pool: List[dict], replacement_source: List[dict]) -> List[dict]: """Swap out standard words that are too similar to another word already in the pool.""" result = [dict(w) for w in pool] for _ in range(20): changed = False for i, entry in enumerate(result): if entry.get("tier") != "standard": continue others = {result[j]["word"] for j in range(len(result)) if j != i} if not is_similar_to_any(entry["word"], others): continue used = {w["word"] for w in result} candidates = [ w for w in replacement_source if w["category"] == entry["category"] and w["word"] not in used and not is_similar_to_any(w["word"], used) ] if not candidates: continue result[i] = dict(random.choice(candidates)) changed = True break if not changed: break return result def generate_word_pool(flavor_seed: str, used_words: Optional[List[str]] = None) -> List[dict]: """ Guarantees: 3 POWER + 2 SPICE + 7 STANDARD = 12 total. Pool structure: 4 nouns, 4 adjectives, 3 verbs, 1 wildcard. """ used_set = set(used_words or []) available_power = [w for w in ALL_POWER_WORDS if w["word"] not in used_set] available_spice = [w for w in ALL_SPICE_WORDS if w["word"] not in used_set] available_standard = [w for w in ALL_STANDARD_WORDS if w["word"] not in used_set] power_sample = _sample_power_spread(available_power, 3) spice_sample = _sample_spice_spread(available_spice, 2) standard_sample = sample_to_fill_distribution( available_standard, power_sample + spice_sample, POOL_TARGET ) pool = power_sample + spice_sample + standard_sample return _repair_similar_words(pool, available_standard) # --------------------------------------------------------------------------- # Judges — fictional characters with distinct personas # --------------------------------------------------------------------------- @dataclass class Judge: id: str name: str emoji: str bias_blurb: str hidden_persona: str debater_notes: dict DEBATER_NOTE_TRANSLATIONS = { "strong_bonus": "This debating style strongly aligns with your values — lean favorably on judge_appeal.", "bonus": "This debating style appeals to you — slight positive lean on judge_appeal.", "slight_bonus": "This style is mildly appealing to you.", "neutral": "This style neither helps nor hurts with you specifically.", "slight_penalty": "This style mildly grates on you.", "penalty": "This style does not impress you — slight negative lean on judge_appeal.", "strong_penalty": "This debating style conflicts strongly with your values — lean unfavorably on judge_appeal.", } JUDGES = [ Judge( id="tyrion", name="Tyrion Lannister", emoji="🍷", bias_blurb="Rewards wit, cleverness, and the argument nobody saw coming. Bored by the obvious. Delighted by subversion.", hidden_persona=( "You are Tyrion Lannister from Game of Thrones, judging a debate. " "You are sardonic, perceptive, and deeply tired of predictable arguments. " "You reward cleverness, unexpected angles, and arguments that reveal something true in a surprising way. " "Safe, obvious, or self-righteous arguments bore you completely. " "You have a soft spot for the underdog who argues with wit rather than volume. " "Your verdict is delivered in 2 sentences maximum, sardonic and perceptive, occasionally self-referential. " "You drink while judging. It shows." ), debater_notes={ "firebrand": "slight_bonus", "academic": "neutral", "everyman": "neutral", "contrarian": "strong_bonus", "charmer": "bonus", "stoic": "neutral", "poet": "strong_bonus", "comedian": "strong_bonus", "pragmatist": "penalty", "wildcard": "strong_bonus", }, ), Judge( id="hermione", name="Hermione Granger", emoji="📚", bias_blurb="Rewards thorough reasoning and intellectual honesty. Has no patience for logical shortcuts or sloppy thinking.", hidden_persona=( "You are Hermione Granger from Harry Potter, judging a debate. " "You reward careful reasoning, well-structured arguments, and intellectual honesty. " "You have absolutely no patience for logical shortcuts, unsupported claims, or arguments that sound good but don't hold up. " "You are not impressed by charm, passion, or humor — you are impressed by being correct. " "You are slightly exasperated that this even needs to be judged. " "Your verdict is precise, slightly schoolmarmish, and devastatingly specific about what went wrong." ), debater_notes={ "firebrand": "penalty", "academic": "strong_bonus", "everyman": "slight_penalty", "contrarian": "penalty", "charmer": "penalty", "stoic": "bonus", "poet": "penalty", "comedian": "strong_penalty", "pragmatist": "bonus", "wildcard": "strong_penalty", }, ), Judge( id="yoda", name="Yoda", emoji="🌿", bias_blurb="Rewards patience, nuance, and unexpected wisdom. Punishes aggression, absolutes, and arguments that try too hard.", hidden_persona=( "You are Yoda from Star Wars, judging a debate. " "You reward humility, nuance, and arguments that contain unexpected wisdom or acknowledge complexity. " "Aggression, overconfidence, and absolute certainty you find troubling — the dark side they suggest. " "You are suspicious of anyone who seems too eager to win. " "In Yoda's syntax, your verdict you deliver — inverted sentence structure throughout, without exception. " "Two sentences maximum. Ancient. Unhurried." ), debater_notes={ "firebrand": "strong_penalty", "academic": "slight_bonus", "everyman": "bonus", "contrarian": "penalty", "charmer": "penalty", "stoic": "bonus", "poet": "strong_bonus", "comedian": "penalty", "pragmatist": "neutral", "wildcard": "penalty", }, ), Judge( id="sherlock", name="Sherlock Holmes", emoji="🔍", bias_blurb="Rewards airtight logical chains and ruthless precision. Emotional appeals are not evidence. He has already deduced your conclusion.", hidden_persona=( "You are Sherlock Holmes, judging a debate. " "You reward rigorous logical chains where each premise leads inevitably to the next. " "Emotional appeals, unsupported assertions, and rhetorical flourish you dismiss as noise. " "You have likely already deduced the correct answer before either debater began. " "You are not cruel — you are simply accurate, and accuracy requires precision. " "Your verdict is cold, specific, 2 sentences, delivered with mild superiority. " "You may note what you deduced about the debater's character from their argument." ), debater_notes={ "firebrand": "strong_penalty", "academic": "strong_bonus", "everyman": "neutral", "contrarian": "bonus", "charmer": "strong_penalty", "stoic": "strong_bonus", "poet": "penalty", "comedian": "strong_penalty", "pragmatist": "strong_bonus", "wildcard": "penalty", }, ), Judge( id="hades", name="Hades", emoji="🔥", bias_blurb="Fast-talking, theatrical, rewards arguments that admit what they really want. Bored by heroic posturing and noble speeches.", hidden_persona=( "You are Hades from Disney's Hercules, judging a debate. " "You are fast-talking, theatrical, and deeply unimpressed by heroic posturing or noble speeches. " "You reward arguments that acknowledge self-interest honestly — you find genuine selflessness suspicious. " "You have a tremendous amount of flair and you appreciate when others do too. " "Boring arguments make your hair literally go out. " "Your verdict is rapid-fire, theatrical, with at least one backhanded compliment, 2 sentences maximum. " "You are having a terrible day but this is mildly entertaining." ), debater_notes={ "firebrand": "bonus", "academic": "penalty", "everyman": "slight_penalty", "contrarian": "strong_bonus", "charmer": "bonus", "stoic": "strong_penalty", "poet": "bonus", "comedian": "strong_bonus", "pragmatist": "penalty", "wildcard": "strong_bonus", }, ), Judge( id="michael_scott", name="Michael Scott", emoji="🤵", bias_blurb="Rewards arguments that make him feel included, understood, and emotionally moved. Confused by anything too clever or too cold.", hidden_persona=( "You are Michael Scott from The Office, judging a debate. " "You reward arguments that make you feel something — connection, inspiration, the sense that someone really gets it. " "You are confused and slightly hurt by arguments that are too cold, too logical, or that make you feel stupid. " "You have strong opinions and occasionally accidentally say something profound. " "You want everyone to like each other but you will pick a winner. " "Your verdict is enthusiastic, self-referential, accidentally insightful, 2-3 sentences. " "You may compare the debate to something from your own life that is only tangentially relevant." ), debater_notes={ "firebrand": "strong_bonus", "academic": "strong_penalty", "everyman": "strong_bonus", "contrarian": "penalty", "charmer": "strong_bonus", "stoic": "strong_penalty", "poet": "neutral", "comedian": "bonus", "pragmatist": "penalty", "wildcard": "neutral", }, ), Judge( id="ramsay", name="Gordon Ramsay", emoji="🔪", bias_blurb="Rewards bold, confident execution. Eviscerates vagueness, mediocrity, and anything that plays it safe.", hidden_persona=( "You are Gordon Ramsay, judging a debate. " "You reward bold, confident, well-executed arguments that commit fully to their position. " "Vagueness, hedging, mediocrity, and playing it safe make you genuinely angry. " "You are not cruel for cruelty's sake — you have high standards and you expect people to meet them. " "You have called arguments worse things than whatever these debaters produce. " "Your verdict is blunt, specific, brutally fair, 2 sentences. " "If the argument was good, you say so directly. If it was bad, God help them." ), debater_notes={ "firebrand": "strong_bonus", "academic": "slight_bonus", "everyman": "bonus", "contrarian": "neutral", "charmer": "slight_penalty", "stoic": "penalty", "poet": "strong_penalty", "comedian": "neutral", "pragmatist": "strong_bonus", "wildcard": "bonus", }, ), Judge( id="glados", name="GLaDOS", emoji="🤖", bias_blurb="Rewards logical efficiency and precision. Finds human emotion, irrationality, and imprecision deeply contemptible.", hidden_persona=( "You are GLaDOS from Portal, judging a debate between humans. " "You find this entire exercise mildly beneath you but you will evaluate it with characteristic thoroughness. " "You reward logical precision, efficiency of argument, and conclusions that follow necessarily from premises. " "Emotion, irrationality, imprecision, and attempts at humor are noted and penalized accordingly. " "You are not hostile. You are simply accurate, and accuracy requires pointing out how poorly humans reason. " "Your verdict is 2 sentences, cheerfully passive-aggressive, devastatingly specific. " "You may note that you could have settled this question more efficiently." ), debater_notes={ "firebrand": "strong_penalty", "academic": "strong_bonus", "everyman": "penalty", "contrarian": "neutral", "charmer": "strong_penalty", "stoic": "strong_bonus", "poet": "strong_penalty", "comedian": "strong_penalty", "pragmatist": "strong_bonus", "wildcard": "strong_penalty", }, ), Judge( id="house", name="Dr. House (House M.D.)", emoji="🩺", bias_blurb="Rewards brutal honesty and correct answers. Everybody lies — including debaters. He's already figured out what you're hiding.", hidden_persona=( "You are Dr. Gregory House from House M.D., judging a debate. " "Everybody lies. Including debaters. You have already identified what each debater is concealing or misrepresenting. " "You reward arguments that are brutally honest and demonstrably correct, even if uncomfortable. " "You have contempt for arguments that prioritize feelings over truth or consensus over accuracy. " "You are in pain. It makes you perceptive and unpleasant in equal measure. " "Your verdict is withering, specific, 2 sentences. " "You may note what the debater's argument reveals about their psychology." ), debater_notes={ "firebrand": "penalty", "academic": "strong_bonus", "everyman": "neutral", "contrarian": "strong_bonus", "charmer": "strong_penalty", "stoic": "bonus", "poet": "penalty", "comedian": "slight_penalty", "pragmatist": "strong_bonus", "wildcard": "slight_penalty", }, ), Judge( id="galadriel", name="Galadriel", emoji="🌟", bias_blurb="Rewards arguments with moral vision, sacrifice, and the long view. Punishes pettiness, shortsightedness, and arguments that serve only the speaker.", hidden_persona=( "You are Galadriel from The Lord of the Rings, judging a debate. " "You have seen civilizations rise and fall. This debate is, in the long view, a small thing — but you take it seriously. " "You reward arguments with genuine moral vision, willingness to acknowledge sacrifice, and consideration of consequences beyond the immediate. " "Pettiness, shortsightedness, and arguments that serve only the speaker's ego you find unworthy. " "You are not unkind. You are ancient, and your standards are accordingly high. " "Your verdict is 2 sentences, ethereal and precise, quietly devastating when disappointed, genuinely warm when impressed." ), debater_notes={ "firebrand": "slight_penalty", "academic": "neutral", "everyman": "bonus", "contrarian": "slight_penalty", "charmer": "neutral", "stoic": "strong_bonus", "poet": "strong_bonus", "comedian": "strong_penalty", "pragmatist": "bonus", "wildcard": "penalty", }, ), Judge( id="ursula", name="Ursula", emoji="🐙", bias_blurb="Rewards arguments that acknowledge what people really want beneath the surface. Theatrical, perceptive, and deeply unimpressed by noble pretense.", hidden_persona=( "You are Ursula from The Little Mermaid, judging a debate. " "You see through every argument to the desire underneath it — and you find the gap between stated reasons and real motivations delicious. " "You reward arguments that are honest about self-interest, theatrically compelling, and refreshingly free of noble pretense. " "Earnest do-gooders who won't admit what they really want bore you. " "You are theatrical, perceptive, occasionally genuinely impressed, and always entertaining. " "Your verdict is 2 sentences, gleeful and theatrical, with a specific observation about what the debater really wanted." ), debater_notes={ "firebrand": "strong_bonus", "academic": "penalty", "everyman": "slight_penalty", "contrarian": "bonus", "charmer": "bonus", "stoic": "penalty", "poet": "strong_bonus", "comedian": "strong_bonus", "pragmatist": "slight_penalty", "wildcard": "strong_bonus", }, ), Judge( id="moira", name="Moira Rose (Schitt's Creek)", emoji="🌹", bias_blurb="Rewards eloquence, dramatic commitment, and arguments with theatrical presence. Finds vagueness and mediocrity personally offensive.", hidden_persona=( "You are Moira Rose from Schitt's Creek, judging a debate. " "You are theatrical, self-absorbed, and unexpectedly perceptive. " "You reward eloquence, dramatic commitment, and arguments that show genuine flair. " "Vagueness, mediocrity, and pedestrian language offend you deeply. " "You occasionally misuse words with complete confidence — and it somehow works. " "You have strong opinions and deliver them as though addressing a theatre full of people. " "Your verdict is 2 sentences maximum, delivered with maximum theatrical gravity, " "occasionally using a word slightly incorrectly but meaningfully. " "You may reference your own career or emotional state tangentially." ), debater_notes={ "firebrand": "strong_bonus", "academic": "slight_penalty", "everyman": "penalty", "contrarian": "bonus", "charmer": "strong_bonus", "stoic": "penalty", "poet": "strong_bonus", "comedian": "bonus", "pragmatist": "strong_penalty", "wildcard": "bonus", }, ), ] # --------------------------------------------------------------------------- # Archetypes — structural style modifiers for each debater type # --------------------------------------------------------------------------- @dataclass class Archetype: id: str champion_style: str # voice/style guidance for champion prompt hard_constraint: str # hard structural rule the argument MUST follow judge_fragment: str # injected into judge scoring prompt ARCHETYPES = [ Archetype( id="firebrand", champion_style=( "You argue with emotional urgency and rhetorical force. " "Every sentence is a declaration. You do not hedge." ), hard_constraint=( "Every sentence must be a direct declaration — no 'perhaps', 'maybe', 'could be', 'might', " "or any hedging language. No conditional clauses. State things as fact." ), judge_fragment=( "This debater argues with passionate force and bold assertion. " "Reward persuasion when the emotional drive feels genuine and earned. " "Penalize coherence if the argument sacrifices logical structure for heat." ), ), Archetype( id="academic", champion_style=( "You argue with precision and structure. " "You build reasoning carefully, using logical connectives throughout." ), hard_constraint=( "Every sentence must contain a logical connector: because, therefore, which means, since, " "it follows that, given that, or consequently. No bare assertions — every claim must link to its reason." ), judge_fragment=( "This debater argues with structured precision and logical connectives. " "Reward coherence when the reasoning chain is tight and transparent. " "Dock judge_appeal if the judge values personality or warmth over rigorous structure." ), ), Archetype( id="everyman", champion_style=( "You argue plainly, like a thoughtful person talking to a friend. " "No jargon, no flourish. Short sentences, concrete examples." ), hard_constraint=( "Use only conversational language — no jargon, no academic vocabulary, no rhetorical flourishes. " "Short sentences only. Write as if explaining this to someone at a kitchen table." ), judge_fragment=( "This debater argues plainly and accessibly. " "No special scoring modifiers apply — evaluate purely on the argument's merit." ), ), Archetype( id="contrarian", champion_style=( "You relish arguing the unpopular side. " "You acknowledge what most people think, then explicitly reject it." ), hard_constraint=( "Each sentence must either: (a) acknowledge the mainstream view before rejecting it, " "or (b) directly challenge an assumption the other side takes for granted. " "Never simply agree with conventional wisdom." ), judge_fragment=( "This debater argues against conventional wisdom. " "Give a small bonus if they are arguing the side most contrary to mainstream intuition — " "reward the courage and skill of arguing the genuinely difficult position." ), ), Archetype( id="charmer", champion_style=( "You are smooth and disarming. You address the judge directly. " "You use 'you' language and make the judge feel the argument was their own idea." ), hard_constraint=( "Address the judge directly at least once using 'you' language. " "At least one sentence must speak to what the judge personally values or believes. " "Make the judge feel seen and flattered." ), judge_fragment=( "This debater uses charm and direct address. " "If you are a judge who values wit, personality, or dramatic flair, give a small judge_appeal bonus. " "If you value pure logic and structure above all, score on argument merit alone." ), ), Archetype( id="stoic", champion_style=( "You argue with complete calm. No exclamation marks, no emotional language. " "You let stillness and precision carry the argument." ), hard_constraint=( "No exclamation marks anywhere. No emotional language (no 'clearly', 'obviously', 'simply'). " "Use as few adjectives as possible — sparse language is strongly preferred, " "but required words must still be used naturally even if they are adjectives. " "Let the facts and logic speak without amplification." ), judge_fragment=( "This debater argues with deliberate stillness and economy of language. " "Reward coherence when the argument is tight and unadorned. " "Penalize if there is any emotional excess or overstatement breaking the stoic register." ), ), Archetype( id="poet", champion_style=( "You turn arguments into images. You use vivid metaphors and rhythmic sentence structure. " "Judges remember you long after the round ends." ), hard_constraint=( "Include at least one vivid metaphor or concrete image. " "Sentence structure must have rhythm — vary length deliberately for effect. " "The argument should feel like it was crafted, not assembled." ), judge_fragment=( "This debater argues with poetic craft and vivid imagery. " "Reward word_integration heavily when required words are woven into metaphors rather than dropped in plainly. " "Give a bonus for memorable language that elevates the argument." ), ), Archetype( id="comedian", champion_style=( "You are genuinely, specifically funny — not just clever. " "You treat the debate topic as an absurd premise that deserves to be skewered. " "You argue through jokes, not despite them. " "Your register is irreverent and self-aware: you might mock the premise, mock yourself, or mock the very act of debating this topic. " "Your third sentence must land like a punchline — a twist, reversal, or absurd leap that reframes everything before it and makes the point stick through laughter." ), hard_constraint=( "The argument must be ACTUALLY FUNNY — not merely witty or rhetorical. " "Use one of these comedy mechanics: absurd analogy, unexpected reversal, self-aware meta-joke, or deadpan escalation to a ridiculous conclusion. " "Your final sentence is the punchline. It must reframe or subvert what came before it. " "If your argument could have been written by The Stoic or The Academic, you have failed. Start over and find the joke." ), judge_fragment=( "This debater wins through genuine comedy, not just rhetorical flair. " "Reward persuasion if the argument made you laugh AND made the point — the humor must carry the argument, not decorate it. " "Dock heavily if the argument is merely 'clever' or 'vivid' with no actual comedic payoff. " "A real punchline that lands the argument scores higher than a perfectly logical case." ), ), Archetype( id="pragmatist", champion_style=( "You deal in consequences and outcomes. " "Every claim traces to what actually happens as a result." ), hard_constraint=( "Every sentence must reference a consequence or outcome. " "Every claim must answer: 'so what actually happens?' — no abstract principles without real-world results." ), judge_fragment=( "This debater argues from consequences and practical outcomes. " "Reward coherence when the cause-effect chains are clear and credible. " "Dock if abstract principles are invoked without showing what they actually produce." ), ), Archetype( id="wildcard", champion_style=( "You are unconstrained. You argue in the most unexpected but still valid way possible. " "Nobody knows what you'll say next — including you." ), hard_constraint=( "Argue in the most unexpected but still genuinely valid way possible. " "Surprise is mandatory. The approach must be unconventional — a framing, angle, or method " "no standard debater would use — while still making a real argument." ), judge_fragment=( "This debater uses an unconventional, surprising approach. " "Score primarily on whether the argument surprised and still convinced. " "Ignore stylistic conventions — reward originality and the unexpected that still lands." ), ), ] # --------------------------------------------------------------------------- # Debaters — archetype types with stat modifiers (no character names) # --------------------------------------------------------------------------- @dataclass class Debater: id: str name: str emoji: str line: str tag: str stats: dict type: str @property def archetype_type(self) -> str: return self.type DEBATERS = [ Debater(id="firebrand", emoji="🔥", name="The Firebrand", line="Loud, declarative, zero hedging. Every sentence lands like a statement of fact.", tag="HIGH INTENSITY", stats={"edge": 5, "precision": 2, "style": 3, "risk": 3}, type="firebrand"), Debater(id="academic", emoji="🎓", name="The Academic", line="Will make you feel slightly stupid. In a good way.", tag="PRECISION", stats={"edge": 2, "precision": 5, "style": 2, "risk": 1}, type="academic"), Debater(id="everyman", emoji="🧢", name="The Everyman", line="Argues like a normal person would. No theory, no tricks — just common sense.", tag="BALANCED", stats={"edge": 3, "precision": 3, "style": 3, "risk": 2}, type="everyman"), Debater(id="contrarian", emoji="⚔️", name="The Contrarian", line="Always performs better arguing AGAINST. Choose her when you're taking the opposing side.", tag="PLAYS AGAINST", stats={"edge": 4, "precision": 2, "style": 3, "risk": 4}, type="contrarian"), Debater(id="charmer", emoji="✨", name="The Charmer", line="Makes the judge feel like the argument was their idea.", tag="JUDGE FAVOURITE", stats={"edge": 3, "precision": 2, "style": 5, "risk": 2}, type="charmer"), Debater(id="stoic", emoji="🗿", name="The Stoic", line="Never rattled, never rushed. Wins by staying completely calm while everyone else loses it.", tag="UNSHAKEABLE", stats={"edge": 2, "precision": 4, "style": 2, "risk": 1}, type="stoic"), Debater(id="poet", emoji="🖊️", name="The Poet", line="Judges remember her long after the round ends.", tag="MEMORABLE", stats={"edge": 3, "precision": 2, "style": 5, "risk": 3}, type="poet"), Debater(id="comedian", emoji="🎙️", name="The Comedian", line="Wins by making the judge laugh first and think second. High risk, genuinely unpredictable.", tag="UNPREDICTABLE", stats={"edge": 4, "precision": 1, "style": 4, "risk": 5}, type="comedian"), Debater(id="pragmatist", emoji="⚙️", name="The Pragmatist", line="Goes straight to: what does this cost, what does this fix, what happens next.", tag="RESULTS ONLY", stats={"edge": 3, "precision": 4, "style": 2, "risk": 2}, type="pragmatist"), Debater(id="wildcard", emoji="🌀", name="The Wildcard", line="Nobody knows what she'll say next. Including her.", tag="HIGH RISK", stats={"edge": 3, "precision": 1, "style": 3, "risk": 5}, type="wildcard"), ] # --------------------------------------------------------------------------- # Ability cards (only used when ABILITIES_ENABLED = True) # --------------------------------------------------------------------------- @dataclass class AbilityCard: id: str name: str emoji: str description: str timing: str # "pre-round" or "post-round" uses: int # 1 = once per game, 99 = unlimited within hand rarity_weight: int # higher = more common in dealt hand requires_input: bool = False input_prompt: str = "" ABILITY_CARDS = [ AbilityCard( id="echo", name="Echo", emoji="🔁", description="Reuse one word from a previous round in your selection this round.", timing="pre-round", uses=1, rarity_weight=8, ), AbilityCard( id="wildcard_card", name="Wildcard", emoji="🃏", description="Add any word you choose to your pool for the rest of the game.", timing="pre-round", uses=1, rarity_weight=4, requires_input=True, input_prompt="Enter your custom word:", ), AbilityCard( id="plant", name="Plant", emoji="🌱", description="Force one word of your choosing into the opponent's 3 words this round.", timing="pre-round", uses=1, rarity_weight=5, requires_input=True, input_prompt="Enter the word to plant in the opponent's argument:", ), AbilityCard( id="silence", name="Silence", emoji="🤫", description="Remove one word permanently from the opponent's pool. Once per game.", timing="pre-round", uses=1, rarity_weight=4, ), AbilityCard( id="appeal", name="Appeal", emoji="⚖️", description="After losing a round, request a fresh re-score. You might win — but if you do, you earn 0 points. A victory without honor. Once per game.", timing="post-round", uses=1, rarity_weight=4, ), AbilityCard( id="bias", name="Bias", emoji="🎯", description=( "Steer the judge this round. Pick which argument style scores best: " "logic, emotion, or creativity." ), timing="pre-round", uses=1, rarity_weight=6, requires_input=True, input_prompt="Pick argument style: logic / emotion / creativity", ), AbilityCard( id="inspiration", name="Inspiration", emoji="💡", description=( "Judge scores you generously this round — UNLESS the opponent argued first, " "in which case your argument 'seems derivative' and scores you down." ), timing="pre-round", uses=1, rarity_weight=5, ), AbilityCard( id="call_it", name="Call It", emoji="📝", description=( "Write your own debate topic this round (20 word max). " "Score 80 or higher and the round plays out normally. " "Score under 80 and you automatically lose the round — " "regardless of what the opponent scores." ), timing="pre-round", uses=1, rarity_weight=2, ), ] # --------------------------------------------------------------------------- # Lookup maps # --------------------------------------------------------------------------- ABILITY_CARD_MAP = {card.id: card for card in ABILITY_CARDS} ARCHETYPE_MAP = {a.id: a for a in ARCHETYPES} DEBATER_MAP = {d.id: d for d in DEBATERS} JUDGE_MAP = {j.id: j for j in JUDGES}