# Effect String Syntax This document covers how effect strings work across the three card tables (citizens, domains, monsters), where the syntax currently diverges, and the proposed unified grammar. --- ## Current state by table ### Citizens — `special_payout_on_turn` / `special_payout_off_turn` | Card | String | Meaning | |---|---|---| | Merchant | `choose g 2 m 2` | Pick one: +2g or +2m | | Mercenary | `exchange s 1 g 2` | Pay 1s, gain 2g | | Champion | `exchange g 1 s 4` | Pay 1g, gain 4s | | Paladin | `exchange s 1 m 3` | Pay 1s, gain 3m | | Butcher | `count owned_worker g 2` | Gain 2g per owned Worker citizen | ### Domains — `activation_effect` | Card | String | Meaning | |---|---|---| | Ancient Tomb | `action.modify_monster_strength +3` | Prompt: add 3 to a monster's strength cost | | Pretorius Conclave | `choose ` | Prompt: take any citizen from the board | | Cursed Cavern | `m 4 + concurrent_flip_one_citizen` | Gain 4m; all players flip a citizen | | Darktide Harbour | `choose ` | Prompt: take a shadow citizen | | Cloudrider's Camp | `s 3 + choose ` | Gain 3s; prompt: take a soldier citizen worth ≤2g | | Wisborg | `manipulate_resources mode=self_convert pay=g:3 gain=v:3 optional=true` | Optionally pay 3g to gain 3vp | ### Domains — `passive_effect` | Card | String | Meaning | |---|---|---| | Jousting Field | `harvest.gain_per_owned_citizen_name Knight g 1` | Harvest phase: gain 1g per Knight owned | | Foxgrove Palisade | `roll.set_one_die target=6 cost=g:2` | Roll phase: pay 2g to set a die to 6 | | The Desert Orchid | `roll.set_one_die target=1 cost=g_per_owned_role:holy_citizen` | Roll phase: pay 1g per holy citizen to set a die to 1 | | Emerald Stronghold | `effect.add action.emeraldstronghold` | Flag: ignore + when buying citizens | | Pratchett's Plateau | `effect.add action.pratchettsplateau` | Flag: domains cost 1g less | | Shelley Commons | `action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1 optional=true` | End of action: optionally pay 1g to a player for 1vp | | Cathedral of St Aquila | `action.end manipulate_resources mode=take_from_player take=g:1 optional=true` | End of action: optionally take 1g from a player | | King Tower | `action.end manipulate_resources mode=pay_to_player gain=v:1 pay=m:1 optional=true` | End of action: optionally pay 1m to a player for 1vp | | The Orb of Urdr | `action.end manipulate_resources mode=take_from_player take=m:1 optional=true` | End of action: optionally take 1m from a player | ### Monsters — `special_reward` | Card | String | Meaning | |---|---|---| | Goblin Mage | `choose g 1 m 1` | Pick one: +1g or +1m | | Goblin Bomber | `choose g 2 m 2 s 2` | Pick one: +2g, +2m, or +2s | | Goblin King | `count area Hills g 1` | Gain 1g per Hills monster slain | | Skeleton King | `count area Ruins g 2` | Gain 2g per Ruins monster slain | | Bane Spider | `choose g 3 ` | Pick one: +3g or take a Knight citizen | | Ettercap | `choose ` | Take a citizen worth ≤2g | | Spider Queen | `choose ` | Pick one: 2g per Forest monster slain, or take a citizen and gain 1vp | | Satyr Mage | `choose g 5 m 5 s 5` | Pick one: +5g, +5m, or +5s | | Troll | `count area Valley m 2` | Gain 2m per Valley monster slain | | Dire Bear | `choose g 2 m 2` | Pick one: +2g or +2m | | Orc Warrior | `choose ` | Take a citizen worth ≤3g | | Orc Batrider | `choose ` | Take any citizen | | Orc Chieftain | `count area Mountain g 2` | Gain 2g per Mountain monster slain | --- ## Where the syntax diverges ### 1. Resource notation — two forms Citizens and monsters use positional shorthand; domain KV pairs use colon notation: ``` # positional (citizens, monsters) choose g 2 m 2 count owned_worker g 2 # colon inside KV values (domain passives) action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1 manipulate_resources mode=self_convert pay=g:3 gain=v:3 roll.set_one_die cost=g:2 ``` ### 2. `count` — same structure, different second word The two count patterns are syntactically parallel but semantically distinct. No unification needed beyond being aware they share a parser. ``` count owned_worker g 2 # count by citizen role owned count area Hills g 1 # count by monster area slain ``` ### 3. `choose` — brackets sometimes, not always The bracket vs no-bracket distinction does carry real meaning and is worth keeping: ``` choose g 1 m 1 # pick one of these resource amounts choose g 3 # pick a resource amount OR an entity choose # pick an entity from a filtered set ``` ### 4. `exchange` — only exists in citizens No equivalent pattern in domains or monsters. Could be expressed as a compound but `exchange` is readable: ``` exchange s 1 g 2 # pay 1s, receive 2g ``` ### 5. `.` is doing three different jobs ``` harvest.gain_per_owned_citizen_name ... # dot = phase separator (phase.verb) roll.set_one_die ... # dot = phase separator (phase.verb) action.end manipulate_resources ... # dot = phase separator, then space, then verb action.modify_monster_strength +3 # dot = namespace separator, not timing effect.add action.emeraldstronghold # dot = verb separator, then dot = namespace ``` ### 6. `manipulate_resources` wrapper verbosity The `mode=` value is doing the same work as a first-word verb. The wrapper adds noise: ``` # current action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1 optional=true # without the wrapper — same information action.end pay_to_player g 1 v 1 optional ``` --- ## Proposed unified grammar ### Core rules 1. **`.` means phase prefix only.** The left side is always a timing trigger (`harvest`, `roll`, `action.end`). Bare verbs have no dot. 2. **Resource amounts are always positional: `g N`.** Colon notation (`g:N`) only appears inside `=` assignments in KV strings where a space would be ambiguous. 3. **`choose` uses brackets for entity picks, bare words for resource picks.** Mixed is allowed: `choose g 3 `. 4. **Compound effects use ` + `.** Each leg is a self-contained effect: `m 4 + concurrent_flip_one_citizen`. ### Proposed rewrites **Domain activation:** ``` # before → after action.modify_monster_strength +3 → modify_monster_strength 3 m 4 + concurrent_flip_one_citizen → no change manipulate_resources mode=self_convert pay=g:3 gain=v:3 ... → self_convert g 3 v 3 optional choose → no change s 3 + choose → no change ``` **Domain passive:** ``` # before → after action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1 optional=true → action.end pay_to_player g 1 v 1 optional action.end manipulate_resources mode=take_from_player take=g:1 optional=true → action.end take_from_player g 1 optional action.end manipulate_resources mode=pay_to_player gain=v:1 pay=m:1 optional=true → action.end pay_to_player m 1 v 1 optional action.end manipulate_resources mode=take_from_player take=m:1 optional=true → action.end take_from_player m 1 optional harvest.gain_per_owned_citizen_name Knight g 1 → no change roll.set_one_die target=6 cost=g:2 → no change effect.add action.emeraldstronghold → no change ``` **Citizens and monsters:** no changes needed — syntax is already consistent within each table and the proposed rules codify what they already do. --- ## What stays as parsed strings vs opaque keys All effects in the tables above are **parsed strings** — a new card with different numbers works without any code change. The only candidates for opaque keys are effects with branching prompt logic unique to a single card that cannot be generalized with different parameters: - `concurrent_flip_one_citizen` (Cursed Cavern) — multi-player concurrent event - `modify_monster_strength 3` (Ancient Tomb) — board-state mutation prompt Even these stay as strings under the unified grammar; they just dispatch to named functions rather than an inline parsing branch. The DB string is the key; the function is the implementation.