# Hallyu Hex Effect Schema v0.1

The `effect_schema` object and each card's `effects` array in
`data/starter-deck.v0.json` form the executable card contract. The schema is
deliberately small: it describes the current 24-card prototype without choosing
an engine implementation or automated-player strategy.

## Resolution model

- Effects and their `actions` resolve in array order.
- Targets are selected before Fandom costs are paid, as required by rules
  version `0.2.0`.
- A failed effect-level `condition` makes the whole effect illegal or inactive.
  A failed action-level `condition` skips only that action.
- `trigger`, `window`, `limit`, `optional`, `activation_cost`, and `for_each`
  describe when an effect can be offered by legal-action enumeration.
- Every effect has a stable card-scoped ID such as `HHX-PROTO-005-E2`.
- Concatenating a card's effect `text` values with one space must exactly equal
  its printable `rules_text`. The validator enforces this link.

The text link catches omitted or orphaned clauses; it does not prove that prose
and data have identical meaning. Changes to either surface still require a
review of the paired effect and a regression test for unusual timing.

## Effect kinds

- `activated`: the controller chooses the effect and pays `activation_cost`.
- `on-enter`: triggers after the card reaches its persistent zone.
- `on-resolve`: performs the normal one-shot card effect. Its default play
  window is `main`; a `play_windows` list records any additional window.
- `replacement`: changes a pending event before it happens.
- `response`: may be declared only in the described `window`.
- `static`: applies while its source remains active and its condition holds.
- `triggered`: is offered when its `trigger` event occurs.

## Actions

The v0.1 vocabulary is:

| Operation | Required fields | Meaning |
| --- | --- | --- |
| `add-counter` | `card`, `counter`, `amount` | Add counters to a public card. |
| `change-tone` | `card`, selectors, `duration` | Replace one tone temporarily. |
| `choose-one` | `player`, `options` | Resolve exactly one legal option. |
| `counter-card` | `card` | Counter a pending card unless an optional payment says otherwise. |
| `deal-damage` | `player`, `amount` | Deal preventable Composure damage. |
| `draw` | `player`, `amount` | Draw, using the empty-deck rule for each failed draw. |
| `gain-resource` | `player`, `resource`, `amount` | Gain Fandom or Trend, respecting caps and terminal checks. |
| `look-at-top` | `players`, `amount` | Privately inspect ordered deck cards. |
| `lose-resource` | `player`, `resource`, `amount` | Lose a resource; Composure loss is not damage. |
| `modify-cost` | `card`, `amount`, `duration` | Apply a cost change, then enforce the zero floor. |
| `move-counter` | counter and card selectors, `amount` | Transfer existing counters. |
| `move-top-to-bottom` | `player` | Move the top deck card to the bottom. |
| `prevent-damage` | `player`, `amount` | Prevent pending or source-matched damage. |
| `redirect-target` | source and destination selectors | Replace one pending legal target. |
| `remove-counter` | `card`, `counter`, `amount` | Pay an activation cost with counters. |
| `return-card` | `card`, `from_zone`, `to_zone` | Move a selected card between zones. |

An integer `amount` is literal. The one v0.1 count expression records cards cast
by a player, filtered by card properties, during the current turn. The string
`all` is valid only for damage prevention.

## Conditions, choices, and replacement outcomes

Conditions are records headed by a versioned `fact`. Comparisons and filters are
data, not Python expressions. An engine must reject unknown facts instead of
guessing.

Structured counter identifiers are lowercase and versioned: `scandal` maps to a
Star's printed starting Scandal value, while `teaser` is created by **Countdown
Teaser Room**. Printable text may capitalize their display names.

The only additional v0.1 play window is `damage-pending-to-controller`.
**Dating Rumor Check** names both `main` and this response window because its
draw choice is always legal while its prevention choice requires pending damage.
Triggers, windows and event-count conditions use only the declared event
vocabulary; the validator rejects an event name the future engine would not
recognize.

Nested action lists appear in three places:

- `choose-one.options[].actions`;
- `deal-damage.on_prevented.actions`, where `replace: true` suppresses the
  original damage before running the alternate actions;
- `counter-card.unless.optional_payment`, where the pending card's controller
  may pay the stated resource to stop the counter.

The validator walks these nested actions, checks their operation vocabulary,
and rejects missing operation fields. A future engine should consume the same
records directly; it should not parse `rules_text`.

## Versioning

`manifest_version`, `rules.version`, and `effect_schema.version` are independent:

- bump `manifest_version` when the manifest structure changes;
- bump `rules.version` when table or engine semantics change;
- bump `effect_schema.version` when an existing effect record could be
  interpreted differently or when its vocabulary changes incompatibly.

The first structured-effects pass uses manifest/rules version `0.2.0` and effect
schema version `0.1.0`. Stable card IDs, card codes, tag IDs, deck IDs, and the
prototype tag namespace are unchanged.
