{"formatVersion":1,"storiesVersion":"2.6.7","actions":[{"type":"setFlag","group":"state","desc":"Set a boolean flag true.","params":"flag","wiring":"reducer"},{"type":"clearFlag","group":"state","desc":"Set a boolean flag false.","params":"flag","wiring":"reducer"},{"type":"incVar","group":"state","desc":"Add to a numeric variable (counters/clocks).","params":"var, by","wiring":"reducer"},{"type":"setVar","group":"state","desc":"Set a variable to a number or string.","params":"var, value","wiring":"reducer"},{"type":"tickClock","group":"state","desc":"Advance a declared clock by N.","params":"clock, by","wiring":"reducer"},{"type":"addClue","group":"state","desc":"Add a clue to the investigation board.","params":"clue","wiring":"reducer"},{"type":"setObjective","group":"state","desc":"Set a quest objective’s status.","params":"objective, to","wiring":"reducer"},{"type":"adjustRelationship","group":"state","desc":"Shift an NPC relationship score.","params":"npc, by","wiring":"reducer"},{"type":"grantAdvantageNext","group":"state","desc":"Advantage on the next check (or initiative).","params":"scope?","wiring":"reducer"},{"type":"grantDisadvantageNext","group":"state","desc":"Disadvantage on the next check (or initiative).","params":"scope?","wiring":"reducer"},{"type":"applyDamage","group":"combat","desc":"Deal damage (dice or amount) to a target.","params":"target, dice?|amount?, damageType?","wiring":"reducer"},{"type":"applyHeal","group":"combat","desc":"Heal a target (dice or amount).","params":"target, dice?|amount?","wiring":"reducer"},{"type":"applyCondition","group":"combat","desc":"Apply a 5e condition to a target.","params":"target, condition","wiring":"reducer"},{"type":"clearCondition","group":"combat","desc":"Remove a condition from a target.","params":"target, condition","wiring":"reducer"},{"type":"requestCheck","group":"combat","desc":"Deprecated — author an ability/skill check as a skillCheck NODE (with outcome tiers), not a then-list action.","params":"ability?|skill?, dc, adv?","wiring":"engine"},{"type":"resolveAbility","group":"combat","desc":"Deprecated — not wired; apply effects via applyDamage/applyHeal/applyCondition (or an item), not this verb.","params":"elementId, targets?","wiring":"engine"},{"type":"startCombat","group":"combat","desc":"Deprecated — begin an encounter via a combat NODE (with onVictory/onDefeat/onFlee), not a then-list action.","params":"encounter","wiring":"engine"},{"type":"grantItem","group":"rewards","desc":"Give an item to the hero’s bag.","params":"itemId, qty?, note?","wiring":"reducer"},{"type":"grantGold","group":"rewards","desc":"Give gold (folds into the purse + ledger).","params":"gp, note?","wiring":"reducer"},{"type":"grantCoins","group":"rewards","desc":"Give mixed coins (cp/sp/ep/gp/pp).","params":"coins, note?","wiring":"reducer"},{"type":"spendCoins","group":"rewards","desc":"Deduct coins (gate with an affords condition).","params":"amount, note?","wiring":"reducer"},{"type":"grantLevel","group":"rewards","desc":"Level the hero up (milestone).","params":"—","wiring":"reducer"},{"type":"awardXp","group":"rewards","desc":"Award XP toward the next level.","params":"xp","wiring":"reducer"},{"type":"craftItem","group":"rewards","desc":"Craft from a recipe — consume the ingredients, grant the output (re-validated against the bag).","params":"recipeId","wiring":"engine"},{"type":"grantInspiration","group":"rewards","desc":"Grant real Heroic Inspiration — spendable at the combat roll windows (✨).","params":"amount?","wiring":"reducer"},{"type":"rollTable","group":"random","desc":"Roll a seeded weighted table and apply the picked entry.","params":"table[], note?","wiring":"engine"},{"type":"callScene","group":"flow","desc":"Jump to a node and push a return address (sub-scene).","params":"node","wiring":"engine"},{"type":"returnScene","group":"flow","desc":"Pop back to the caller of a sub-scene.","params":"—","wiring":"engine"},{"type":"ifThen","group":"flow","desc":"Run `then` (else `else`) by an inline condition — conditional effects inside a then-list.","params":"if, then, else?","wiring":"engine"},{"type":"consumeItem","group":"sheet","desc":"Consume a single-use item the hero holds.","params":"itemId, qty?","wiring":"reducer"},{"type":"useItem","group":"sheet","desc":"Use an owned item — a PLAYER command from the bag/inventory (not an author then-list action).","params":"itemId","wiring":"command"},{"type":"spendResource","group":"sheet","desc":"Spend a resource/slot the hero actually has.","params":"key, amount?","wiring":"reducer"},{"type":"revealObject","group":"scene","desc":"Reveal a hidden scene object.","params":"objectId","wiring":"reducer"},{"type":"setObjectState","group":"scene","desc":"Set a scene object’s state key (drives layer swaps).","params":"objectId, key, value","wiring":"reducer"},{"type":"moveActor","group":"scene","desc":"Move an actor to a zone.","params":"actorId, zoneId","wiring":"reducer"},{"type":"setActorVisibility","group":"scene","desc":"Set an actor visible/hidden/concealed.","params":"actorId, visibility","wiring":"reducer"},{"type":"goto","group":"flow","desc":"Jump to another node.","params":"node","wiring":"engine"},{"type":"emitEvent","group":"io","desc":"Emit a domain event (analytics / AI / voice hook seam).","params":"name, props?","wiring":"runner"},{"type":"showScene","group":"io","desc":"Show a scene on the shared display.","params":"sceneRef","wiring":"runner"},{"type":"playAudio","group":"io","desc":"Play a soundboard clip.","params":"trackId","wiring":"runner"},{"type":"showToast","group":"io","desc":"Float a short message to the player (\"You found 10 silver\").","params":"text, ms?","wiring":"runner"},{"type":"equipItem","group":"sheet","desc":"Equip an item the hero OWNS (grant it first) — auto-picks the slot, or takes an explicit one. Runs through the same equip rules as the inventory.","params":"itemId, slot?","wiring":"runner"}],"conditions":[{"key":"all","desc":"All sub-conditions are true (AND).","params":"all: Condition[]"},{"key":"any","desc":"Any sub-condition is true (OR).","params":"any: Condition[]"},{"key":"not","desc":"The sub-condition is false (NOT).","params":"not: Condition"},{"key":"flag","desc":"A flag equals a boolean.","params":"flag, is"},{"key":"hasFlag","desc":"A flag is truthy (shorthand).","params":"hasFlag"},{"key":"var","desc":"A variable compares (==,!=,>,>=,<,<=) to a value.","params":"var, op, value"},{"key":"clock","desc":"A clock is within gte/lte bounds.","params":"clock, gte?, lte?"},{"key":"hasClue","desc":"The hero has learned a clue.","params":"hasClue"},{"key":"objective","desc":"An objective has a given status.","params":"objective, is"},{"key":"relationship","desc":"An NPC relationship is within gte/lte bounds.","params":"relationship, gte?, lte?"},{"key":"hasItem","desc":"The hero holds an item (optional qty).","params":"hasItem, qty?"},{"key":"affords","desc":"The purse can afford a coin cost.","params":"affords: {cp?,sp?,ep?,gp?,pp?}"},{"key":"visited","desc":"A node has been entered at least once.","params":"visited: NodeId"},{"key":"visitCount","desc":"A node’s visit count is within gte/lte bounds.","params":"visitCount, gte?, lte?"},{"key":"inZone","desc":"The hero is in a given scene zone.","params":"inZone: zoneId"},{"key":"objectState","desc":"A scene object’s state key matches (default: revealed).","params":"objectState: {id, key?, is}"},{"key":"stat","desc":"Compare a character stat (level / xp / hp / maxHp).","params":"stat, op, value"}],"triggers":[{"key":"onEnterNode","fires":true,"desc":"When a node is entered."},{"key":"onExitNode","fires":false,"desc":"When a node is left (reserved)."},{"key":"onChoice","fires":false,"desc":"When a choice is taken (reserved)."},{"key":"onCheckTier","fires":false,"desc":"When a check resolves to a tier (reserved)."},{"key":"onCombatEnd","fires":false,"desc":"When combat ends (reserved)."},{"key":"onFlagSet","fires":false,"desc":"When a flag is set (reserved)."},{"key":"onVarChange","fires":false,"desc":"When a variable changes (reserved)."},{"key":"onZoneEnter","fires":true,"desc":"When the hero moves into a scene zone (gate with inZone)."},{"key":"onAttacked","fires":false,"desc":"In-combat reaction window — reserved/deferred (task #52)."}],"nodeKinds":[{"kind":"scene","desc":"Narrative beat: prose + optional interactables / passive onEnterCheck → next."},{"kind":"choice","desc":"A branch: a list of choices (each with optional requires / then / next)."},{"kind":"dialogue","desc":"A conversation: conditional lines + response choices in one node."},{"kind":"skillCheck","desc":"An ability/skill check with 5 outcome tiers + a failure model."},{"kind":"challenge","desc":"A skill challenge: reach N successes before M failures."},{"kind":"combat","desc":"An encounter → onVictory / onDefeat / onFlee."},{"kind":"loot","desc":"Grant rewards (then actions) → next."},{"kind":"transition","desc":"A conditional jump to another node."},{"kind":"gate","desc":"First matching branch wins; else a fallback node."},{"kind":"consequence","desc":"Apply effects (then actions) → next."},{"kind":"rest","desc":"A short/long rest (spend Hit Dice / refresh resources) → next."},{"kind":"hub","desc":"A revisitable hub with conditional exits."},{"kind":"ending","desc":"A terminal node (endingId + outcome + epilogue variants)."}],"wizardPatterns":[{"id":"relationship-domino","name":"Relationship Domino","purpose":"The signature companion arc: a bond you build early reads back as a rescue, a combat ally, and a stay-or-leave payoff.","whenToUse":"You want a companion whose relationship with the hero visibly changes outcomes across the story (the \"Mira chain\").","requiredAnchors":["influenceSceneId (bond starts — choice/dialogue)","assistSceneId (a skillCheck the companion can rescue)","combatSceneId (a fight they join)","payoffSceneId (the stay/leave moment)"],"optionalParams":["relationshipKind: trust|respect|debt|romance|rivalry","npcName","createMissingStubs (stub any missing anchor)"],"generatedState":["a relationship variable (e.g. <prefix>_trust) written at the bond + read at the assist, combat, and payoff"],"expectedPayoff":"The payoff beat’s DC bends on the relationship var, and the ending/late scene differs by whether the bond was earned — not a cosmetic flag.","examplePrompt":"\"Add a companion the player can earn the trust of, who saves them on a check, fights beside them, and reacts at the end.\"","exampleOutputShape":"1 relationship var + up to 4 wired beats (influence → assist allyAssist → combat ally → dcAdjust payoff) + a test preset."},{"id":"romance-rivalry","name":"Romance / Rivalry Turn","purpose":"A relationship escalation that turns on a check: a quiet moment builds it, a turn beat resolves it into devotion or a sworn rivalry.","whenToUse":"You want one charged relationship beat whose result (won-over vs turned-away) the story remembers.","requiredAnchors":["momentSceneId (the quiet-moment/rising-tension beat — choice/dialogue)","turnSceneId (the turn — a skillCheck)"],"optionalParams":["kind: romance|rivalry","npcName","createMissingStubs"],"generatedState":["a relationship flag/var raised at the moment + read (dcAdjust) at the turn + an outcome var set by the turn"],"expectedPayoff":"The turn’s DC is easier if the bond was built; the outcome var is available for later scenes/endings to read.","examplePrompt":"\"Give the rival a scene where the player can win them over or make an enemy for good.\"","exampleOutputShape":"1–2 relationship vars + 2 wired beats (moment → dcAdjust turn) + optional battle-respect consequence."},{"id":"faction-favor","name":"Faction Favor","purpose":"A faction ledger: do a job to earn favor, optionally get their forces in a fight, then spend the standing at a payoff.","whenToUse":"You want a faction whose regard for the player is earned and later cashed in (allies, prices, access).","requiredAnchors":["jobSceneId (the favor/job — a skillCheck)","payoffSceneId (collecting the debt — a favor-aware skillCheck)"],"optionalParams":["forcesRef (a stat block the faction sends to fight beside you)","combatSceneId (where they join)","createMissingStubs"],"generatedState":["a favor relationship var (e.g. <prefix>_favor) + a job-done flag"],"expectedPayoff":"The payoff DC reads the favor var and the job-done flag; faction forces appear in combat only once the job is done.","examplePrompt":"\"Add a thieves’ guild the player can do a job for, then call in a favor later.\"","exampleOutputShape":"1 favor var + 1 flag + 2–3 beats (job → optional combat ally → favor-aware payoff)."},{"id":"civilian","name":"Civilian Consequence","purpose":"A bystander moral choice the world remembers: help or exploit, and a later beat reflects it.","whenToUse":"You want a low-cost choice that proves the world reacts — cheap reactivity that seeds the \"choices matter\" feel.","requiredAnchors":["choiceSceneId (the help-or-exploit moment — a choice)","payoffSceneId (where the outcome shows — any prose beat)"],"optionalParams":["civilianName","createMissingStubs"],"generatedState":["an outcome var set to \"helped\" or \"harmed\" at the choice, read in conditional prose at the payoff"],"expectedPayoff":"The payoff prose changes on the outcome var (via {{ }} binding) — a remembered consequence, not a dead flag.","examplePrompt":"\"Let the player help or rob a refugee, and have it come back later.\"","exampleOutputShape":"1 outcome var + 2 beats (binary choice → conditional-prose payoff)."},{"id":"ending-callback","name":"Ending Callback (Montage)","purpose":"An epilogue montage: each thread the player resolved appends its own line to the ending.","whenToUse":"You have several setups (companions, factions, civilians) and want the ending to pay them ALL back in one montage.","requiredAnchors":["endingId (which ending carries the montage — defaults to the first ending)"],"optionalParams":["threads: [{ flagId, label }] (the setups to call back)","createMissingStubs"],"generatedState":["reads existing thread flags — declares none of its own (it is the payoff layer)"],"expectedPayoff":"Each thread flag conditionally appends a montage line to the ending body — the structural home for \"every setup pays off\".","examplePrompt":"\"At the end, show what happened to each companion and faction based on my choices.\"","exampleOutputShape":"Conditional prose lines appended to one ending, one per thread flag (no new schema)."}],"modules":[{"id":"store","name":"Shop / Merchant","purpose":"A full buy/sell/haggle/steal shop with a merchant purse, per-item stock, and a buy-back ledger.","whenToUse":"The party needs a place to spend/earn coin with real economy (finite merchant funds, price negotiation).","requiredAnchors":["a callScene entry from a hub/town beat"],"optionalParams":["merchant name","items[]","merchantGp (finite purse)","haggle","steal","discountPct/bestDiscountPct","exitTo"],"generatedState":["prefix-namespaced stock + buy-back + friendly-price vars (auto-scoped module-local)"],"expectedPayoff":"Coin becomes a real resource — the economy metric (currency_flow) reflects it; pairs with quest rewards + crafting.","examplePrompt":"\"Add a general store in town where you can buy gear, sell loot, and haggle.\"","exampleOutputShape":"A shop dialogue sub-scene (callScene/returnScene) + stock/purse vars + a module contract."},{"id":"quest-board","name":"Quest Board","purpose":"A board of postings that activate objectives (quest-log entries) with optional gated, rewarded turn-ins + chains.","whenToUse":"You want to hand the player optional quests with prerequisites and structured completion.","requiredAnchors":["a callScene entry from a hub/town beat"],"optionalParams":["board name","postings[] (each: objective, title, requires, turnInRequires, reward)","exitTo"],"generatedState":["one objective var per posting (hidden→active→done) — the quest log"],"expectedPayoff":"Objectives are optional-quest optionality the World model + health read; turn-ins grant real rewards.","examplePrompt":"\"Put a bounty board in the tavern with three side quests, one that needs proof to turn in.\"","exampleOutputShape":"A board sub-scene + N objective vars + gated take/turn-in choices + a module contract."},{"id":"crafting-station","name":"Crafting Station","purpose":"A forge/bench that offers recipes from deps.recipes, gating each on ingredient availability, firing craftItem.","whenToUse":"You want crafting: consume materials → produce an item, with honest \"you lack X\" locks.","requiredAnchors":["a callScene entry","recipes declared in deps.recipes"],"optionalParams":["station name","recipeIds[]","exitTo"],"generatedState":["no new vars — reads the bag; fires craftItem (consume → grant)"],"expectedPayoff":"Ties the economy loop together (store buys materials → craft → use); lint flags unobtainable ingredients.","examplePrompt":"\"Add a smithy where you can forge a silver sword if you have silver and a blade.\"","exampleOutputShape":"A forge sub-scene + one gated choice per recipe + a module contract."},{"id":"region","name":"Explorable Region (Dungeon / Woods)","purpose":"An explore hub: a weighted table rolls a random encounter (combat that loops back), a find (loot/clue), or nothing, with an anti-grind depth cap.","whenToUse":"You want a replayable explorable area (a crypt, a forest) rather than a fixed corridor.","requiredAnchors":["a callScene entry from the map/travel beat"],"optionalParams":["name","flavor","encounters[] (weighted stat blocks)","finds[] (weighted loot/clue)","nothingWeight","softCap (explores before exhaustion)","kind: dungeon|woods (re-skins labels)"],"generatedState":["a prefix-namespaced depth counter with a soft cap"],"expectedPayoff":"Non-linear optional content the reachability/never-visited metrics reward; encounters reuse deps.statBlocks.","examplePrompt":"\"Make the Sunken Crypt explorable with random undead fights and loot, that eventually empties out.\"","exampleOutputShape":"An explore hub sub-scene + rollTable encounters/finds + a depth counter + a module contract. (woods = same scaffold, forest skin.)"}],"note":"The closed authoring vocabulary, derived from the single-source catalog. Default-deny: any verb not in `actions` is rejected by the validator and cannot execute. `wizardPatterns` + `modules` are reusable recipes you can build from their contracts (purpose/whenToUse/requiredAnchors/generatedState/expectedPayoff) — prefer a recipe over hand-composing primitives when one fits. This is the grounding surface for AI authoring (so a model cannot invent a verb)."}