weiss_core/
state.rs

1use crate::db::{AbilitySpec, CardId};
2use crate::effects::{EffectId, EffectPayload, EffectSpec, ReplacementSpec};
3use crate::error::StateError;
4use crate::util::Rng64;
5use serde::{Deserialize, Serialize};
6use std::collections::VecDeque;
7
8/// Unique identifier for a card instance within a game.
9pub type CardInstanceId = u32;
10
11/// Concrete card instance with ownership and controller.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct CardInstance {
14    /// Static card identifier in the card database.
15    pub id: CardId,
16    /// Stable per-game identifier for this physical card instance.
17    pub instance_id: CardInstanceId,
18    /// Owning seat (0 or 1).
19    pub owner: u8,
20    /// Current controlling seat (0 or 1).
21    pub controller: u8,
22}
23
24/// Max number of reveal history entries tracked per player.
25pub const REVEAL_HISTORY_LEN: usize = 8;
26
27/// Ring buffer of recently revealed cards.
28#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
29pub struct RevealHistory {
30    entries: [CardId; REVEAL_HISTORY_LEN],
31    len: u8,
32    head: u8,
33}
34
35impl RevealHistory {
36    /// Create an empty reveal history.
37    pub fn new() -> Self {
38        Self {
39            entries: [0; REVEAL_HISTORY_LEN],
40            len: 0,
41            head: 0,
42        }
43    }
44
45    /// Push a newly revealed card into the history.
46    pub fn push(&mut self, card: CardId) {
47        let head = self.head as usize;
48        self.entries[head] = card;
49        if (self.len as usize) < REVEAL_HISTORY_LEN {
50            self.len = self.len.saturating_add(1);
51        }
52        self.head = ((head + 1) % REVEAL_HISTORY_LEN) as u8;
53    }
54
55    /// Write entries in chronological order into `out`.
56    pub fn write_chronological(&self, out: &mut [i32]) {
57        out.fill(0);
58        let len = self.len as usize;
59        if len == 0 {
60            return;
61        }
62        let start = if len < REVEAL_HISTORY_LEN {
63            0
64        } else {
65            self.head as usize
66        };
67        for idx in 0..len.min(out.len()) {
68            let entry_idx = if len < REVEAL_HISTORY_LEN {
69                idx
70            } else {
71                (start + idx) % REVEAL_HISTORY_LEN
72            };
73            out[idx] = self.entries[entry_idx] as i32;
74        }
75    }
76}
77
78impl Default for RevealHistory {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl CardInstance {
85    /// Create a new card instance owned by `owner`.
86    pub fn new(id: CardId, owner: u8, instance_id: CardInstanceId) -> Self {
87        Self {
88            id,
89            instance_id,
90            owner,
91            controller: owner,
92        }
93    }
94}
95
96/// Turn phase.
97#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
98pub enum Phase {
99    /// Mulligan step before the first turn begins.
100    Mulligan,
101    /// Stand phase: stand rested characters.
102    Stand,
103    /// Draw phase: draw a card.
104    Draw,
105    /// Clock phase: optionally place a card into clock.
106    Clock,
107    /// Main phase: play cards and use main-phase abilities.
108    Main,
109    /// Climax phase: optionally place a climax.
110    Climax,
111    /// Attack phase.
112    Attack,
113    /// End phase cleanup.
114    End,
115}
116
117/// Timing window for triggered effects.
118#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
119pub enum TimingWindow {
120    /// Main phase timing window.
121    MainWindow,
122    /// Climax phase timing window.
123    ClimaxWindow,
124    /// After an attack is declared.
125    AttackDeclarationWindow,
126    /// During trigger reveal/resolution.
127    TriggerResolutionWindow,
128    /// During counter timing.
129    CounterWindow,
130    /// During damage resolution.
131    DamageResolutionWindow,
132    /// During encore timing.
133    EncoreWindow,
134    /// During end phase timing.
135    EndPhaseWindow,
136}
137
138/// Stage slot status.
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
140pub enum StageStatus {
141    /// Standing (upright) character.
142    Stand,
143    /// Rested (tapped) character.
144    Rest,
145    /// Reversed (defeated) character.
146    Reverse,
147}
148
149/// Stage slot containing a character or empty.
150#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
151pub struct StageSlot {
152    /// Occupying card instance, if any.
153    pub card: Option<CardInstance>,
154    /// Marker cards attached to the occupying character.
155    #[serde(default)]
156    pub markers: Vec<CardInstance>,
157    /// Current stand/rest/reverse status.
158    pub status: StageStatus,
159    /// Whether the current card was played from hand this turn.
160    #[serde(default)]
161    pub played_from_hand_this_turn: bool,
162    /// Battle-only power modifier.
163    pub power_mod_battle: i32,
164    /// Turn-long power modifier.
165    pub power_mod_turn: i32,
166    /// Whether this slot has attacked this turn.
167    pub has_attacked: bool,
168    /// Whether this slot is prevented from attacking.
169    pub cannot_attack: bool,
170    /// Additional stock cost required to declare an attack.
171    pub attack_cost: u8,
172}
173
174impl StageSlot {
175    /// Create an empty stage slot.
176    pub fn empty() -> Self {
177        Self {
178            card: None,
179            markers: Vec::new(),
180            status: StageStatus::Stand,
181            played_from_hand_this_turn: false,
182            power_mod_battle: 0,
183            power_mod_turn: 0,
184            has_attacked: false,
185            cannot_attack: false,
186            attack_cost: 0,
187        }
188    }
189
190    /// Whether the slot is empty.
191    pub fn is_empty(&self) -> bool {
192        self.card.is_none()
193    }
194}
195
196/// Attack types available during the attack step.
197#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
198pub enum AttackType {
199    /// Frontal attack against an opposing character.
200    Frontal,
201    /// Side attack against an opposing character.
202    Side,
203    /// Direct attack (no opposing character).
204    Direct,
205}
206
207/// Attack step sub-phase.
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
209pub enum AttackStep {
210    /// Trigger reveal/resolution step.
211    Trigger,
212    /// Counter timing step.
213    Counter,
214    /// Damage resolution step.
215    Damage,
216    /// Battle comparison step.
217    Battle,
218    /// Encore timing step.
219    Encore,
220}
221
222/// Damage type classification.
223#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
224pub enum DamageType {
225    /// Damage caused by an attack.
226    Battle,
227    /// Damage caused by an effect.
228    Effect,
229}
230
231/// Modifier categories for damage processing.
232#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
233pub enum DamageModifierKind {
234    /// Add `delta` to the damage amount.
235    AddAmount {
236        /// Signed delta to apply.
237        delta: i32,
238    },
239    /// Set whether the damage is cancelable.
240    SetCancelable {
241        /// New cancelable flag.
242        cancelable: bool,
243    },
244    /// Cancel the next damage instance.
245    CancelNext,
246    /// Set the damage amount to an absolute value.
247    SetAmount {
248        /// Absolute damage amount.
249        amount: i32,
250    },
251}
252
253/// Applied damage modifier instance.
254#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
255pub struct DamageModifier {
256    /// Modifier behavior.
257    pub kind: DamageModifierKind,
258    /// Ordering priority for application.
259    pub priority: i16,
260    /// Insertion order used as a tie-breaker.
261    pub insertion: u32,
262    /// Source identifier for debugging/auditing.
263    pub source_id: u32,
264    /// Remaining applications or magnitude budget (variant-dependent).
265    pub remaining: i32,
266    /// Whether this modifier has been applied at least once.
267    pub used: bool,
268}
269
270/// Trigger effects resolved from trigger icons.
271#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
272pub enum TriggerEffect {
273    /// Add soul for the current attack.
274    Soul,
275    /// Draw a card.
276    Draw,
277    /// Deal shot damage.
278    Shot,
279    /// Return a character to hand.
280    Bounce,
281    /// Perform a choice selection.
282    Choice,
283    /// Add the revealed card to stock ("Pool").
284    Pool,
285    /// Add the revealed card to hand ("Treasure").
286    Treasure,
287    /// Salvage from waiting room ("Gate").
288    Gate,
289    /// Place a character from deck ("Standby").
290    Standby,
291    /// Resolve an auto ability on the trigger source card.
292    AutoAbility {
293        /// Index into the card's ability list.
294        ability_index: u8,
295    },
296    /// Resolve an auto ability that was granted at runtime.
297    GrantedAutoAbility {
298        /// Stable grant identifier referencing a `GrantedAbilityInstance`.
299        grant_id: u64,
300    },
301}
302
303/// Zones that can be targeted by effects.
304#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
305pub enum TargetZone {
306    /// Stage (front/back row slots).
307    Stage,
308    /// Hand.
309    Hand,
310    /// Top of deck.
311    DeckTop,
312    /// Clock.
313    Clock,
314    /// Level zone.
315    Level,
316    /// Stock.
317    Stock,
318    /// Memory.
319    Memory,
320    /// Waiting room.
321    WaitingRoom,
322    /// Climax zone.
323    Climax,
324    /// Resolution zone (temporary).
325    Resolution,
326}
327
328/// Side selection for targeting.
329#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
330pub enum TargetSide {
331    /// Current player / controller side.
332    SelfSide,
333    /// Opponent side.
334    Opponent,
335}
336
337/// Slot filter for targeting.
338#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
339pub enum TargetSlotFilter {
340    /// Any slot (no restriction).
341    Any,
342    /// Front-row slots only.
343    FrontRow,
344    /// Back-row slots only.
345    BackRow,
346    /// A specific slot index.
347    SpecificSlot(
348        /// Slot index in `[0, 4]`.
349        u8,
350    ),
351}
352
353/// Targeting specification for effects.
354#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
355pub struct TargetSpec {
356    /// Target zone to search/select from.
357    pub zone: TargetZone,
358    /// Which side to target.
359    pub side: TargetSide,
360    /// Optional slot filter (primarily for stage targeting).
361    pub slot_filter: TargetSlotFilter,
362    /// Optional card type restriction.
363    pub card_type: Option<crate::db::CardType>,
364    /// Optional trait restriction (packed trait id).
365    #[serde(default)]
366    pub card_trait: Option<u16>,
367    /// Optional inclusive maximum level restriction.
368    pub level_max: Option<u8>,
369    /// Optional inclusive maximum cost restriction.
370    #[serde(default)]
371    pub cost_max: Option<u8>,
372    /// Optional card id whitelist restriction.
373    #[serde(default)]
374    pub card_ids: Vec<CardId>,
375    /// Number of cards/targets to select.
376    pub count: u8,
377    /// Optional hard limit for search-like effects.
378    #[serde(default)]
379    pub limit: Option<u8>,
380    /// If true, only the source card is eligible.
381    #[serde(default)]
382    pub source_only: bool,
383    /// If true, reveal selected cards to the controller.
384    #[serde(default)]
385    pub reveal_to_controller: bool,
386}
387
388/// Concrete target reference.
389#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
390pub struct TargetRef {
391    /// Owning/located player seat (0 or 1).
392    pub player: u8,
393    /// Zone containing the card.
394    pub zone: TargetZone,
395    /// Index within the zone (slot or list position).
396    pub index: u8,
397    /// Static card id.
398    pub card_id: CardId,
399    /// Stable per-game card instance id.
400    pub instance_id: CardInstanceId,
401}
402
403/// Pending target effect awaiting resolution.
404#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
405pub enum PendingTargetEffect {
406    /// Resolve an effect payload once targeting is complete.
407    EffectPending {
408        /// Effect instance id to associate with the payload.
409        instance_id: u32,
410        /// Effect payload to execute.
411        payload: EffectPayload,
412    },
413}
414
415/// State for a target-selection prompt.
416#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
417pub struct TargetSelectionState {
418    /// Controlling player making selections.
419    pub controller: u8,
420    /// Source card id producing the prompt.
421    pub source_id: CardId,
422    /// Targeting specification to satisfy.
423    pub spec: TargetSpec,
424    /// Remaining selections required.
425    pub remaining: u8,
426    /// Selected targets so far.
427    pub selected: Vec<TargetRef>,
428    /// Optional precomputed candidate list (for pagination/debugging).
429    #[serde(default)]
430    pub candidates: Vec<TargetRef>,
431    /// Effect to apply once selection completes.
432    pub effect: PendingTargetEffect,
433    /// Whether the controller may skip instead of selecting.
434    pub allow_skip: bool,
435}
436
437/// Item on the effect stack.
438#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
439pub struct StackItem {
440    /// Unique stack item id.
441    pub id: u32,
442    /// Controlling player for resolution ordering.
443    pub controller: u8,
444    /// Source card id that created the stack item.
445    pub source_id: CardId,
446    /// Compiled effect id for this item.
447    pub effect_id: EffectId,
448    /// Payload to resolve.
449    pub payload: EffectPayload,
450}
451
452/// Priority window state.
453#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
454pub struct PriorityState {
455    /// Current priority holder seat.
456    pub holder: u8,
457    /// Consecutive pass count (for window termination).
458    pub passes: u8,
459    /// Active timing window.
460    pub window: TimingWindow,
461    /// Bitmask of already-used ACT abilities (by index).
462    pub used_act_mask: u32,
463}
464
465/// Stack ordering state when multiple items are pending.
466#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
467pub struct StackOrderState {
468    /// Group identifier for the ordering prompt.
469    pub group_id: u32,
470    /// Controller making the ordering decision.
471    pub controller: u8,
472    /// Items to order.
473    pub items: Vec<StackItem>,
474}
475
476/// Reasons for prompting a choice.
477#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
478pub enum ChoiceReason {
479    /// Choose a standby target from trigger effect.
480    TriggerStandbySelect,
481    /// Choose a treasure target from trigger effect.
482    TriggerTreasureSelect,
483    /// Choose whether to resolve a draw trigger (and/or its target).
484    TriggerDrawSelect,
485    /// Choose a target for a choice trigger.
486    TriggerChoiceSelect,
487    /// Choose targets for an auto ability cost step.
488    TriggerAutoCostSelect,
489    /// Choose whether to draw after a brainstorm effect.
490    BrainstormDrawSelect,
491    /// Choose an ordering for simultaneous stack items.
492    StackOrderSelect,
493    /// Choose an action during a priority window.
494    PriorityActionSelect,
495    /// Choose a payment option for a staged cost.
496    CostPayment,
497    /// Choose effect targets.
498    TargetSelect,
499    /// Choose cards to discard during end phase.
500    EndPhaseDiscard,
501}
502
503/// Cost payment step kinds.
504#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
505pub enum CostStepKind {
506    /// Rest another character.
507    RestOther,
508    /// Sacrifice a character from stage.
509    SacrificeFromStage,
510    /// Discard a card from hand.
511    DiscardFromHand,
512    /// Clock a card from hand.
513    ClockFromHand,
514    /// Clock the top card(s) of the deck.
515    ClockFromDeckTop,
516    /// Reveal a card from hand.
517    RevealFromHand,
518}
519
520/// Result to execute when staged cost payment finishes.
521#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
522pub enum CostPaymentOutcome {
523    /// Continue by resolving the originating ability.
524    #[default]
525    ResolveAbility,
526    /// Keep a character for encore by selecting the specified slot.
527    EncoreKeep {
528        /// Stage slot index being kept for encore.
529        slot: u8,
530    },
531}
532
533/// State for a multi-step cost payment.
534#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
535pub struct CostPaymentState {
536    /// Controlling player paying the cost.
537    pub controller: u8,
538    /// Source card id of the ability being paid for.
539    pub source_id: CardId,
540    /// Source card instance id (stable per-game).
541    pub source_instance_id: CardInstanceId,
542    /// Optional source slot when the source is on stage.
543    pub source_slot: Option<u8>,
544    /// Ability index on the source card.
545    pub ability_index: u8,
546    /// Remaining cost to pay.
547    pub remaining: crate::db::AbilityCost,
548    /// Currently active staged step, if any.
549    pub current_step: Option<CostStepKind>,
550    /// Outcome to execute once the cost is fully paid.
551    #[serde(default)]
552    pub outcome: CostPaymentOutcome,
553}
554
555/// Zones that choices can draw from.
556#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
557pub enum ChoiceZone {
558    /// Waiting room.
559    WaitingRoom,
560    /// Stage.
561    Stage,
562    /// Hand.
563    Hand,
564    /// Top of deck.
565    DeckTop,
566    /// Clock.
567    Clock,
568    /// Level zone.
569    Level,
570    /// Stock.
571    Stock,
572    /// Memory.
573    Memory,
574    /// Climax zone.
575    Climax,
576    /// Resolution zone.
577    Resolution,
578    /// Effect stack.
579    Stack,
580    /// Priority window: counter action.
581    PriorityCounter,
582    /// Priority window: ACT action.
583    PriorityAct,
584    /// Priority window: pass action.
585    PriorityPass,
586    /// Skip / decline an optional choice.
587    Skip,
588}
589
590/// Reference to a concrete choice option.
591#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
592pub struct ChoiceOptionRef {
593    /// Static card id for the referenced option.
594    pub card_id: CardId,
595    /// Stable per-game card instance id.
596    pub instance_id: CardInstanceId,
597    /// Zone the option is sourced from.
598    pub zone: ChoiceZone,
599    /// Optional index within the zone (for list-like zones).
600    pub index: Option<u16>,
601    /// Optional target slot index (for stage-based options).
602    pub target_slot: Option<u8>,
603}
604
605/// Choice prompt state for a player.
606#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
607pub struct ChoiceState {
608    /// Unique choice id.
609    pub id: u32,
610    /// Reason this choice is being requested.
611    pub reason: ChoiceReason,
612    /// Player seat making the choice.
613    pub player: u8,
614    /// Candidate options in the current page.
615    pub options: Vec<ChoiceOptionRef>,
616    /// Total candidates available across all pages.
617    pub total_candidates: u16,
618    /// Start offset of the current page.
619    pub page_start: u16,
620    /// Optional trigger associated with this choice (when choosing trigger order/targets).
621    pub pending_trigger: Option<PendingTrigger>,
622}
623
624/// Context for an ongoing attack.
625#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
626pub struct AttackContext {
627    /// Attacker stage slot index.
628    pub attacker_slot: u8,
629    /// Optional defender stage slot index (None for direct attacks).
630    pub defender_slot: Option<u8>,
631    /// Declared attack type.
632    pub attack_type: AttackType,
633    /// Trigger-check revealed card id (if any).
634    pub trigger_card: Option<CardId>,
635    /// Trigger-check revealed card instance id (if any).
636    pub trigger_instance_id: Option<CardInstanceId>,
637    /// Total trigger checks required.
638    pub trigger_checks_total: u8,
639    /// Trigger checks already resolved.
640    pub trigger_checks_resolved: u8,
641    /// Current damage amount.
642    pub damage: i32,
643    /// Whether counter is allowed.
644    pub counter_allowed: bool,
645    /// Whether a counter was played.
646    pub counter_played: bool,
647    /// Power added by counters.
648    pub counter_power: i32,
649    /// Active damage modifiers.
650    pub damage_modifiers: Vec<DamageModifier>,
651    /// Pending shot damage remaining to apply.
652    pub pending_shot_damage: u8,
653    /// Next id for damage modifier instances within this attack.
654    pub next_modifier_id: u32,
655    /// Last damage event id emitted for this attack (if any).
656    pub last_damage_event_id: Option<u32>,
657    /// Whether auto triggers were enqueued for this attack.
658    pub auto_trigger_enqueued: bool,
659    /// Whether auto damage effects were enqueued for this attack.
660    pub auto_damage_enqueued: bool,
661    /// Whether battle damage has been applied.
662    pub battle_damage_applied: bool,
663    /// Current sub-step within the attack.
664    pub step: AttackStep,
665    /// Whether the declaration timing window is complete.
666    pub decl_window_done: bool,
667    /// Whether the trigger timing window is complete.
668    pub trigger_window_done: bool,
669    /// Whether the damage timing window is complete.
670    pub damage_window_done: bool,
671}
672
673/// Trigger pending resolution.
674#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
675pub struct PendingTrigger {
676    /// Unique trigger id.
677    pub id: u32,
678    /// Group id for simultaneous triggers.
679    pub group_id: u32,
680    /// Player seat that owns the trigger.
681    pub player: u8,
682    /// Source card id that produced the trigger.
683    pub source_card: CardId,
684    /// Trigger effect kind.
685    pub effect: TriggerEffect,
686    /// Optional effect id for auto/granted abilities.
687    pub effect_id: Option<EffectId>,
688}
689
690/// Ordering state for multiple triggers.
691#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
692pub struct TriggerOrderState {
693    /// Group id for the set of triggers being ordered.
694    pub group_id: u32,
695    /// Player seat choosing the order.
696    pub player: u8,
697    /// Remaining trigger ids to choose from.
698    pub choices: Vec<u32>,
699}
700
701/// Derived attack information for a single slot.
702#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
703pub struct DerivedAttackSlot {
704    /// Whether the slot is unable to declare any attack.
705    pub cannot_attack: bool,
706    /// Whether side attacks are disallowed.
707    #[serde(default)]
708    pub cannot_side_attack: bool,
709    /// Whether frontal attacks are disallowed.
710    #[serde(default)]
711    pub cannot_frontal_attack: bool,
712    /// Additional stock cost required to attack from this slot.
713    pub attack_cost: u8,
714}
715
716impl DerivedAttackSlot {
717    /// Create an empty derived attack slot.
718    pub fn empty() -> Self {
719        Self {
720            cannot_attack: false,
721            cannot_side_attack: false,
722            cannot_frontal_attack: false,
723            attack_cost: 0,
724        }
725    }
726}
727
728/// Derived attack state for a turn.
729#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
730pub struct DerivedAttackState {
731    /// Per-player derived slot info for each stage slot.
732    pub per_player: [[DerivedAttackSlot; 5]; 2],
733}
734
735impl DerivedAttackState {
736    /// Create a default derived attack state.
737    pub fn new() -> Self {
738        Self {
739            per_player: [[DerivedAttackSlot::empty(); 5]; 2],
740        }
741    }
742}
743
744impl Default for DerivedAttackState {
745    fn default() -> Self {
746        Self::new()
747    }
748}
749
750/// Encore request tracking for a character.
751#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
752pub struct EncoreRequest {
753    /// Player seat that owns the encore request.
754    pub player: u8,
755    /// Stage slot index of the character requesting encore.
756    pub slot: u8,
757}
758
759/// Terminal outcome for an episode.
760#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
761pub enum TerminalResult {
762    /// A player won the episode.
763    Win {
764        /// Winning player seat (0 or 1).
765        winner: u8,
766    },
767    /// Both players reached a draw condition.
768    Draw,
769    /// Episode ended due to reaching a tick/decision limit.
770    Timeout,
771}
772
773/// Full per-player state.
774#[derive(Clone, Debug, Hash)]
775pub struct PlayerState {
776    /// Deck (top at end of vector).
777    pub deck: Vec<CardInstance>,
778    /// Hand.
779    pub hand: Vec<CardInstance>,
780    /// Waiting room.
781    pub waiting_room: Vec<CardInstance>,
782    /// Clock.
783    pub clock: Vec<CardInstance>,
784    /// Level zone.
785    pub level: Vec<CardInstance>,
786    /// Stock (top at end of vector).
787    pub stock: Vec<CardInstance>,
788    /// Memory.
789    pub memory: Vec<CardInstance>,
790    /// Climax zone.
791    pub climax: Vec<CardInstance>,
792    /// Resolution zone (temporary).
793    pub resolution: Vec<CardInstance>,
794    /// Stage slots (5 total).
795    pub stage: [StageSlot; 5],
796}
797
798impl PlayerState {
799    /// Create a new player state with an initial deck.
800    pub fn new(deck: Vec<CardInstance>) -> Self {
801        Self {
802            deck,
803            hand: Vec::new(),
804            waiting_room: Vec::new(),
805            clock: Vec::new(),
806            level: Vec::new(),
807            stock: Vec::new(),
808            memory: Vec::new(),
809            climax: Vec::new(),
810            resolution: Vec::new(),
811            stage: [
812                StageSlot::empty(),
813                StageSlot::empty(),
814                StageSlot::empty(),
815                StageSlot::empty(),
816                StageSlot::empty(),
817            ],
818        }
819    }
820}
821
822/// Modifier kinds applied to cards or zones.
823#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
824pub enum ModifierKind {
825    /// Modify card power.
826    Power,
827    /// Modify card soul.
828    Soul,
829    /// Modify effective level.
830    Level,
831    /// Modify stock cost to attack.
832    AttackCost,
833    /// Prevent declaring attacks.
834    CannotAttack,
835    /// Prevent declaring side attacks.
836    CannotSideAttack,
837    /// Prevent declaring frontal attacks.
838    CannotFrontalAttack,
839    /// Prevent a character from becoming reversed.
840    CannotBecomeReverse,
841    /// Prevent being chosen by opponent effects.
842    CannotBeChosenByOpponentEffects,
843    /// Prevent moving stage position.
844    CannotMoveStagePosition,
845    /// Prevent playing events from hand.
846    CannotPlayEventsFromHand,
847    /// Prevent playing backup from hand.
848    CannotPlayBackupFromHand,
849    /// Prevent standing during the stand phase.
850    CannotStandDuringStandPhase,
851    /// On reverse, move the battle opponent to memory.
852    BattleOpponentMoveToMemoryOnReverse,
853    /// Modify stock cost to encore.
854    EncoreStockCost,
855}
856
857/// Modifier duration semantics.
858#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
859pub enum ModifierDuration {
860    /// Expires during end-of-turn cleanup.
861    UntilEndOfTurn,
862    /// Persists while the card remains on stage.
863    WhileOnStage,
864}
865
866/// Modifier layer for ordering purposes.
867#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
868pub enum ModifierLayer {
869    /// Continuous effects.
870    Continuous,
871    /// Effect resolution layer (default).
872    #[default]
873    Effect,
874}
875
876/// Concrete modifier instance applied to a target.
877#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
878pub struct ModifierInstance {
879    /// Unique modifier id.
880    pub id: u32,
881    /// Source card id that created the modifier.
882    pub source: CardId,
883    /// Optional source slot when the source is on stage.
884    #[serde(default)]
885    pub source_slot: Option<u8>,
886    /// Target player seat.
887    pub target_player: u8,
888    /// Target stage slot index.
889    pub target_slot: u8,
890    /// Target card id (for debugging and validation).
891    pub target_card: CardId,
892    /// Modifier kind.
893    pub kind: ModifierKind,
894    /// Modifier magnitude (kind-dependent).
895    pub magnitude: i32,
896    /// Duration for which the modifier remains active.
897    pub duration: ModifierDuration,
898    /// Layer used when applying modifiers.
899    #[serde(default)]
900    pub layer: ModifierLayer,
901    /// Insertion order used as a tie-breaker.
902    pub insertion: u32,
903}
904
905/// Runtime granted ability attached to a specific stage card instance.
906#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
907pub struct GrantedAbilityInstance {
908    /// Stable grant id.
909    pub grant_id: u64,
910    /// Target player seat.
911    pub target_player: u8,
912    /// Target stage slot index.
913    pub target_slot: u8,
914    /// Target card instance id.
915    pub target_instance_id: CardInstanceId,
916    /// Ability spec (template + conditions + cost).
917    pub spec: AbilitySpec,
918    /// Compiled effect specs derived from `spec`.
919    pub compiled_effects: Vec<EffectSpec>,
920    /// Turn number at which this grant expires during end-phase cleanup.
921    pub expires_turn_number: u32,
922}
923
924/// Turn-level state tracking.
925#[derive(Clone, Debug, Hash)]
926pub struct TurnState {
927    /// Seat whose turn it currently is.
928    pub active_player: u8,
929    /// Seat that started the game.
930    pub starting_player: u8,
931    /// Turn counter (starting at 0 for mulligan).
932    pub turn_number: u32,
933    /// Current phase within the turn.
934    pub phase: Phase,
935    /// Per-seat mulligan completion flags.
936    pub mulligan_done: [bool; 2],
937    /// Per-seat packed mulligan selections.
938    pub mulligan_selected: [u64; 2],
939    /// Whether the main phase has been passed.
940    pub main_passed: bool,
941    /// Decision counter for the episode.
942    pub decision_count: u32,
943    /// Tick counter for the episode.
944    pub tick_count: u32,
945    /// Active attack context, if any.
946    pub attack: Option<AttackContext>,
947    /// Counter used to advance attack subphases deterministically.
948    pub attack_subphase_count: u8,
949    /// Seat that must level up next, if any.
950    pub pending_level_up: Option<u8>,
951    /// Queue of encore requests to resolve.
952    pub encore_queue: Vec<EncoreRequest>,
953    /// Seat currently choosing encore, if any.
954    pub encore_step_player: Option<u8>,
955    /// Pending triggers awaiting ordering/resolution.
956    pub pending_triggers: Vec<PendingTrigger>,
957    /// Whether `pending_triggers` is already sorted for resolution.
958    pub pending_triggers_sorted: bool,
959    /// Current timing window for priority/auto effects.
960    pub active_window: Option<TimingWindow>,
961    /// Whether end-phase window has been processed.
962    pub end_phase_window_done: bool,
963    /// Whether end-phase discard has been processed.
964    pub end_phase_discard_done: bool,
965    /// Whether end-phase climax cleanup has been processed.
966    pub end_phase_climax_done: bool,
967    /// Whether end-phase general cleanup has been processed.
968    pub end_phase_cleanup_done: bool,
969    /// Whether encore window has been processed.
970    pub encore_window_done: bool,
971    /// Per-seat pending loss flags.
972    pub pending_losses: [bool; 2],
973    /// Seat currently taking damage, if any.
974    pub damage_resolution_target: Option<u8>,
975    /// Nested cost payment depth (re-entrancy guard).
976    pub cost_payment_depth: u8,
977    /// Pending cleanup operations for the resolution zone.
978    pub pending_resolution_cleanup: Vec<(u8, CardInstanceId)>,
979    /// Per-seat flag to disable auto-encore.
980    pub cannot_use_auto_encore: [bool; 2],
981    /// Active rule overrides for this turn.
982    pub rule_overrides: Vec<crate::effects::RuleOverrideKind>,
983    /// Runtime granted abilities active this turn.
984    pub granted_abilities: Vec<GrantedAbilityInstance>,
985    /// Next grant id allocator.
986    pub next_grant_id: u64,
987    /// Internal step counter within the current phase.
988    pub phase_step: u8,
989    /// Whether attack-phase begin effects have run.
990    pub attack_phase_begin_done: bool,
991    /// Whether attack declaration legality checks have run.
992    pub attack_decl_check_done: bool,
993    /// Whether encore begin effects have run.
994    pub encore_begin_done: bool,
995    /// Active trigger ordering prompt, if any.
996    pub trigger_order: Option<TriggerOrderState>,
997    /// Active choice prompt, if any.
998    pub choice: Option<ChoiceState>,
999    /// Active target selection prompt, if any.
1000    pub target_selection: Option<TargetSelectionState>,
1001    /// Active cost payment state, if any.
1002    pub pending_cost: Option<CostPaymentState>,
1003    /// Active priority window, if any.
1004    pub priority: Option<PriorityState>,
1005    /// Effect stack items awaiting resolution.
1006    pub stack: Vec<StackItem>,
1007    /// Pending stack groups awaiting an ordering decision.
1008    pub pending_stack_groups: VecDeque<StackOrderState>,
1009    /// Active stack ordering prompt, if any.
1010    pub stack_order: Option<StackOrderState>,
1011    /// Cached derived attack state, if any.
1012    pub derived_attack: Option<DerivedAttackState>,
1013    /// Next trigger id allocator.
1014    pub next_trigger_id: u32,
1015    /// Next trigger group id allocator.
1016    pub next_trigger_group_id: u32,
1017    /// Next choice id allocator.
1018    pub next_choice_id: u32,
1019    /// Next stack group id allocator.
1020    pub next_stack_group_id: u32,
1021    /// Next damage event id allocator.
1022    pub next_damage_event_id: u32,
1023    /// Next effect instance id allocator.
1024    pub next_effect_instance_id: u32,
1025    /// Whether the end phase is currently pending execution.
1026    pub end_phase_pending: bool,
1027}
1028
1029/// Complete game state for an environment.
1030#[derive(Clone, Debug, Hash)]
1031pub struct GameState {
1032    /// Per-seat player state.
1033    pub players: [PlayerState; 2],
1034    /// Per-seat reveal history.
1035    pub reveal_history: [RevealHistory; 2],
1036    /// Turn-level state.
1037    pub turn: TurnState,
1038    /// Deterministic RNG state.
1039    pub rng: Rng64,
1040    /// Active modifier instances.
1041    pub modifiers: Vec<ModifierInstance>,
1042    /// Next modifier id allocator.
1043    pub next_modifier_id: u32,
1044    /// Active replacement specs.
1045    pub replacements: Vec<ReplacementSpec>,
1046    /// Insertion order counter for replacements.
1047    pub next_replacement_insertion: u32,
1048    /// Terminal result for the episode, if any.
1049    pub terminal: Option<TerminalResult>,
1050}
1051
1052impl GameState {
1053    /// Build a new game state with the given decks and seed.
1054    pub fn new(
1055        deck_a: Vec<CardId>,
1056        deck_b: Vec<CardId>,
1057        seed: u64,
1058        starting_player: u8,
1059    ) -> Result<Self, StateError> {
1060        if starting_player > 1 {
1061            return Err(StateError::InvalidStartingPlayer {
1062                got: starting_player,
1063            });
1064        }
1065        if deck_a.len() != crate::encode::MAX_DECK {
1066            return Err(StateError::DeckLength {
1067                owner: 0,
1068                got: deck_a.len(),
1069                expected: crate::encode::MAX_DECK,
1070            });
1071        }
1072        if deck_b.len() != crate::encode::MAX_DECK {
1073            return Err(StateError::DeckLength {
1074                owner: 1,
1075                got: deck_b.len(),
1076                expected: crate::encode::MAX_DECK,
1077            });
1078        }
1079        let rng = Rng64::new(seed);
1080        let mut next_instance_id: CardInstanceId = 1;
1081        let deck_a = Self::build_deck(deck_a, 0, &mut next_instance_id);
1082        let deck_b = Self::build_deck(deck_b, 1, &mut next_instance_id);
1083        Ok(Self {
1084            players: [PlayerState::new(deck_a), PlayerState::new(deck_b)],
1085            reveal_history: [RevealHistory::new(), RevealHistory::new()],
1086            turn: TurnState {
1087                active_player: starting_player,
1088                starting_player,
1089                turn_number: 0,
1090                phase: Phase::Mulligan,
1091                mulligan_done: [false; 2],
1092                mulligan_selected: [0; 2],
1093                main_passed: false,
1094                decision_count: 0,
1095                tick_count: 0,
1096                attack: None,
1097                attack_subphase_count: 0,
1098                pending_level_up: None,
1099                encore_queue: Vec::new(),
1100                encore_step_player: None,
1101                pending_triggers: Vec::new(),
1102                pending_triggers_sorted: true,
1103                trigger_order: None,
1104                choice: None,
1105                target_selection: None,
1106                pending_cost: None,
1107                priority: None,
1108                stack: Vec::new(),
1109                pending_stack_groups: VecDeque::new(),
1110                stack_order: None,
1111                derived_attack: None,
1112                next_trigger_id: 1,
1113                next_trigger_group_id: 1,
1114                next_choice_id: 1,
1115                next_stack_group_id: 1,
1116                next_damage_event_id: 1,
1117                next_effect_instance_id: 1,
1118                active_window: None,
1119                end_phase_window_done: false,
1120                end_phase_discard_done: false,
1121                end_phase_climax_done: false,
1122                end_phase_cleanup_done: false,
1123                encore_window_done: false,
1124                pending_losses: [false; 2],
1125                damage_resolution_target: None,
1126                cost_payment_depth: 0,
1127                pending_resolution_cleanup: Vec::new(),
1128                cannot_use_auto_encore: [false; 2],
1129                rule_overrides: Vec::new(),
1130                granted_abilities: Vec::new(),
1131                next_grant_id: 1,
1132                phase_step: 0,
1133                attack_phase_begin_done: false,
1134                attack_decl_check_done: false,
1135                encore_begin_done: false,
1136                end_phase_pending: false,
1137            },
1138            rng,
1139            modifiers: Vec::new(),
1140            next_modifier_id: 1,
1141            replacements: Vec::new(),
1142            next_replacement_insertion: 1,
1143            terminal: None,
1144        })
1145    }
1146
1147    /// Compatibility helper for test/bench scaffolding.
1148    pub fn new_or_panic(
1149        deck_a: Vec<CardId>,
1150        deck_b: Vec<CardId>,
1151        seed: u64,
1152        starting_player: u8,
1153    ) -> Self {
1154        Self::new(deck_a, deck_b, seed, starting_player).expect("GameState::new_or_panic failed")
1155    }
1156
1157    fn build_deck(
1158        deck: Vec<CardId>,
1159        owner: u8,
1160        next_instance_id: &mut CardInstanceId,
1161    ) -> Vec<CardInstance> {
1162        deck.into_iter()
1163            .map(|id| {
1164                let instance_id = *next_instance_id;
1165                *next_instance_id = next_instance_id.wrapping_add(1);
1166                CardInstance::new(id, owner, instance_id)
1167            })
1168            .collect()
1169    }
1170}
1171
1172#[cfg(test)]
1173mod tests {
1174    use super::{RevealHistory, REVEAL_HISTORY_LEN};
1175
1176    #[test]
1177    fn reveal_history_chronology_before_wrap() {
1178        let mut history = RevealHistory::new();
1179        history.push(10);
1180        history.push(20);
1181        history.push(30);
1182        let mut out = [0i32; REVEAL_HISTORY_LEN];
1183        history.write_chronological(&mut out);
1184        assert_eq!(&out[..3], &[10, 20, 30]);
1185        assert!(out[3..].iter().all(|entry| *entry == 0));
1186    }
1187
1188    #[test]
1189    fn reveal_history_chronology_after_wrap_keeps_latest_entries() {
1190        let mut history = RevealHistory::new();
1191        for card in 1..=(REVEAL_HISTORY_LEN as u32 + 3) {
1192            history.push(card);
1193        }
1194        let mut out = [0i32; REVEAL_HISTORY_LEN];
1195        history.write_chronological(&mut out);
1196        let expected: Vec<i32> = (4..=(REVEAL_HISTORY_LEN as i32 + 3)).collect();
1197        assert_eq!(out.as_slice(), expected.as_slice());
1198    }
1199}