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}