battleware_execution/
lib.rs

1use battleware_types::{
2    execution::{
3        Account, Creature, Event, Instruction, Key, Leaderboard, Outcome, Output, Transaction,
4        Value, LOBBY_EXPIRY, MAX_BATTLE_ROUNDS, MAX_LOBBY_SIZE, MOVE_EXPIRY, TOTAL_MOVES,
5    },
6    Seed,
7};
8use bytes::{Buf, BufMut};
9use commonware_codec::{Encode, EncodeSize, Error, Read, ReadExt, Write};
10use commonware_consensus::threshold_simplex::types::View;
11use commonware_cryptography::{
12    bls12381::{
13        primitives::variant::{MinSig, Variant},
14        tle::{decrypt, Ciphertext},
15    },
16    ed25519::PublicKey,
17    sha256::{Digest, Sha256},
18    Hasher,
19};
20#[cfg(feature = "parallel")]
21use commonware_runtime::ThreadPool;
22use commonware_runtime::{Clock, Metrics, Spawner, Storage};
23use commonware_storage::{adb::any::variable::Any, translator::Translator};
24use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
25#[cfg(feature = "parallel")]
26use rayon::iter::{IntoParallelIterator, ParallelIterator};
27use std::{
28    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
29    future::Future,
30};
31
32mod elo;
33mod fixed;
34pub mod state_transition;
35
36#[cfg(any(test, feature = "mocks"))]
37pub mod mocks;
38
39pub type Adb<E, T> = Any<E, Digest, Value, Sha256, T>;
40
41pub trait State {
42    fn get(&self, key: &Key) -> impl Future<Output = Option<Value>>;
43    fn insert(&mut self, key: Key, value: Value) -> impl Future<Output = ()>;
44    fn delete(&mut self, key: &Key) -> impl Future<Output = ()>;
45
46    fn apply(&mut self, changes: Vec<(Key, Status)>) -> impl Future<Output = ()> {
47        async {
48            for (key, status) in changes {
49                match status {
50                    Status::Update(value) => self.insert(key, value).await,
51                    Status::Delete => self.delete(&key).await,
52                }
53            }
54        }
55    }
56}
57
58impl<E: Spawner + Metrics + Clock + Storage, T: Translator> State for Adb<E, T> {
59    async fn get(&self, key: &Key) -> Option<Value> {
60        let key = Sha256::hash(&key.encode());
61        self.get(&key).await.unwrap()
62    }
63
64    async fn insert(&mut self, key: Key, value: Value) {
65        let key = Sha256::hash(&key.encode());
66        self.update(key, value).await.unwrap();
67    }
68
69    async fn delete(&mut self, key: &Key) {
70        let key = Sha256::hash(&key.encode());
71        self.delete(key).await.unwrap();
72    }
73}
74
75#[derive(Default)]
76pub struct Memory {
77    state: HashMap<Key, Value>,
78}
79
80impl State for Memory {
81    async fn get(&self, key: &Key) -> Option<Value> {
82        self.state.get(key).cloned()
83    }
84
85    async fn insert(&mut self, key: Key, value: Value) {
86        self.state.insert(key, value);
87    }
88
89    async fn delete(&mut self, key: &Key) {
90        self.state.remove(key);
91    }
92}
93
94#[derive(Clone)]
95#[allow(clippy::large_enum_variant)]
96pub enum Status {
97    Update(Value),
98    Delete,
99}
100
101impl Write for Status {
102    fn write(&self, writer: &mut impl BufMut) {
103        match self {
104            Status::Update(value) => {
105                0u8.write(writer);
106                value.write(writer);
107            }
108            Status::Delete => 1u8.write(writer),
109        }
110    }
111}
112
113impl Read for Status {
114    type Cfg = ();
115
116    fn read_cfg(reader: &mut impl Buf, _: &Self::Cfg) -> Result<Self, Error> {
117        let kind = u8::read(reader)?;
118        match kind {
119            0 => Ok(Status::Update(Value::read(reader)?)),
120            1 => Ok(Status::Delete),
121            _ => Err(Error::InvalidEnum(kind)),
122        }
123    }
124}
125
126impl EncodeSize for Status {
127    fn encode_size(&self) -> usize {
128        1 + match self {
129            Status::Update(value) => value.encode_size(),
130            Status::Delete => 0,
131        }
132    }
133}
134
135pub async fn nonce<S: State>(state: &S, public: &PublicKey) -> u64 {
136    let account =
137        if let Some(Value::Account(account)) = state.get(&Key::Account(public.clone())).await {
138            account
139        } else {
140            Account::default()
141        };
142    account.nonce
143}
144
145pub struct Noncer<'a, S: State> {
146    state: &'a S,
147    pending: BTreeMap<Key, Status>,
148}
149
150impl<'a, S: State> Noncer<'a, S> {
151    pub fn new(state: &'a S) -> Self {
152        Self {
153            state,
154            pending: BTreeMap::new(),
155        }
156    }
157
158    pub async fn prepare(&mut self, transaction: &Transaction) -> bool {
159        let mut account = if let Some(Value::Account(account)) =
160            self.get(&Key::Account(transaction.public.clone())).await
161        {
162            account
163        } else {
164            Account::default()
165        };
166
167        // Ensure nonce is correct
168        if account.nonce != transaction.nonce {
169            return false;
170        }
171
172        // Increment nonce
173        account.nonce += 1;
174        self.insert(
175            Key::Account(transaction.public.clone()),
176            Value::Account(account),
177        )
178        .await;
179
180        true
181    }
182}
183
184impl<'a, S: State> State for Noncer<'a, S> {
185    async fn get(&self, key: &Key) -> Option<Value> {
186        match self.pending.get(key) {
187            Some(Status::Update(value)) => Some(value.clone()),
188            Some(Status::Delete) => None,
189            None => self.state.get(key).await,
190        }
191    }
192
193    async fn insert(&mut self, key: Key, value: Value) {
194        self.pending.insert(key, Status::Update(value));
195    }
196
197    async fn delete(&mut self, key: &Key) {
198        self.pending.insert(key.clone(), Status::Delete);
199    }
200}
201
202#[derive(Hash, Eq, PartialEq)]
203#[allow(clippy::large_enum_variant)]
204enum Task {
205    Seed(Seed),
206    Decrypt(Seed, Ciphertext<MinSig>),
207}
208
209enum TaskResult {
210    Seed(bool),
211    Decrypt([u8; 32]),
212}
213
214pub struct Layer<'a, S: State> {
215    state: &'a S,
216    pending: BTreeMap<Key, Status>,
217
218    master: <MinSig as Variant>::Public,
219    namespace: Vec<u8>,
220
221    seed: Seed,
222
223    precomputations: HashMap<Task, TaskResult>,
224}
225
226impl<'a, S: State> Layer<'a, S> {
227    pub fn new(
228        state: &'a S,
229        master: <MinSig as Variant>::Public,
230        namespace: &[u8],
231        seed: Seed,
232    ) -> Self {
233        let mut verified_seeds = HashSet::new();
234        verified_seeds.insert(seed.clone());
235        Self {
236            state,
237            pending: BTreeMap::new(),
238
239            master,
240            namespace: namespace.to_vec(),
241
242            seed,
243
244            precomputations: HashMap::new(),
245        }
246    }
247
248    fn insert(&mut self, key: Key, value: Value) {
249        self.pending.insert(key, Status::Update(value));
250    }
251
252    fn delete(&mut self, key: Key) {
253        self.pending.insert(key, Status::Delete);
254    }
255
256    pub fn view(&self) -> View {
257        self.seed.view
258    }
259
260    async fn prepare(&mut self, transaction: &Transaction) -> bool {
261        // Get account
262        let mut account = if let Some(Value::Account(account)) =
263            self.get(&Key::Account(transaction.public.clone())).await
264        {
265            account
266        } else {
267            Account::default()
268        };
269
270        // Ensure nonce is correct
271        if account.nonce != transaction.nonce {
272            return false;
273        }
274
275        // Increment nonce
276        account.nonce += 1;
277        self.insert(
278            Key::Account(transaction.public.clone()),
279            Value::Account(account),
280        );
281
282        true
283    }
284
285    async fn extract(&mut self, transaction: &Transaction) -> Vec<Task> {
286        match &transaction.instruction {
287            Instruction::Generate => vec![],
288            Instruction::Match => vec![],
289            Instruction::Move(_) => vec![],
290            Instruction::Settle(signature) => {
291                // Get account
292                let Some(Value::Account(account)) =
293                    self.get(&Key::Account(transaction.public.clone())).await
294                else {
295                    return vec![];
296                };
297
298                // If not in a battle, not valid
299                let Some(battle) = account.battle else {
300                    return vec![];
301                };
302
303                // Get battle
304                let Some(Value::Battle {
305                    expiry,
306                    player_a_pending,
307                    player_b_pending,
308                    ..
309                }) = self.get(&Key::Battle(battle)).await
310                else {
311                    return vec![];
312                };
313
314                // If turn has not expired, not valid
315                if expiry > self.seed.view {
316                    return vec![];
317                }
318
319                // Extract seed and decryptions
320                let seed = Seed::new(expiry, *signature);
321                let mut ops = vec![Task::Seed(seed.clone())];
322                if let Some(pending) = player_a_pending {
323                    ops.push(Task::Decrypt(seed.clone(), pending));
324                }
325                if let Some(pending) = player_b_pending {
326                    ops.push(Task::Decrypt(seed, pending));
327                }
328
329                ops
330            }
331        }
332    }
333
334    async fn apply(&mut self, transaction: &Transaction) -> Vec<Event> {
335        // Get account
336        let Some(Value::Account(mut account)) =
337            self.get(&Key::Account(transaction.public.clone())).await
338        else {
339            panic!("Account should exist");
340        };
341
342        // Execute instruction
343        let mut events = vec![];
344        match &transaction.instruction {
345            Instruction::Generate => {
346                // If in a battle, not valid
347                if account.battle.is_some() {
348                    return events;
349                }
350
351                // Generate a new creature
352                let creature = Creature::new(
353                    transaction.public.clone(),
354                    transaction.nonce,
355                    self.seed.signature,
356                );
357                account.creature = Some(creature.clone());
358
359                // Store update in account
360                self.insert(
361                    Key::Account(transaction.public.clone()),
362                    Value::Account(account),
363                );
364                events.push(Event::Generated {
365                    account: transaction.public.clone(),
366                    creature,
367                });
368            }
369            Instruction::Match => {
370                // If not locked on a creature, not valid
371                if account.creature.is_none() {
372                    return events;
373                }
374
375                // If already in a battle, not valid
376                if account.battle.is_some() {
377                    return events;
378                }
379
380                // Get lobby
381                let Some(Value::Lobby {
382                    expiry,
383                    mut players,
384                }) = self.get(&Key::Lobby).await
385                else {
386                    // Create lobby
387                    let mut players = BTreeSet::new();
388                    players.insert(transaction.public.clone());
389                    let lobby = Value::Lobby {
390                        expiry: self
391                            .seed
392                            .view
393                            .checked_add(LOBBY_EXPIRY)
394                            .expect("view overflow"),
395                        players,
396                    };
397                    self.insert(Key::Lobby, lobby);
398                    return events;
399                };
400
401                // Add to lobby.
402                //
403                // If already in lobby, that's fine (may trigger matching).
404                players.insert(transaction.public.clone());
405
406                // If lobby has expired or is full, create matches
407                if expiry < self.seed.view || players.len() >= MAX_LOBBY_SIZE {
408                    // Get players
409                    let mut players = players.iter().collect::<Vec<_>>();
410
411                    // Randomly select trainers
412                    let seed = Sha256::hash(self.seed.encode().as_ref());
413                    let mut rng = StdRng::from_seed(seed.as_ref().try_into().unwrap());
414                    players.shuffle(&mut rng);
415
416                    // Create match
417                    let iter = players.chunks_exact(2).map(|chunk| (chunk[0], chunk[1]));
418                    let mut hasher = Sha256::new();
419                    for (player_a, player_b) in iter {
420                        // Compute battle key
421                        hasher.update(self.seed.encode().as_ref());
422                        hasher.update(player_a.as_ref());
423                        hasher.update(player_b.as_ref());
424                        let key = hasher.finalize();
425
426                        // Add battle to player A
427                        let Some(Value::Account(mut account_a)) =
428                            self.get(&Key::Account(player_a.clone())).await
429                        else {
430                            panic!("Player A should have an account");
431                        };
432                        let player_a_creature = account_a.creature.as_ref().unwrap().clone();
433                        let player_a_stats = account_a.stats.clone();
434                        let player_a_health = player_a_creature.health();
435                        account_a.battle = Some(key);
436                        self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
437
438                        // Add battle to player B
439                        let Some(Value::Account(mut account_b)) =
440                            self.get(&Key::Account(player_b.clone())).await
441                        else {
442                            panic!("Player B should have an account");
443                        };
444                        let player_b_creature = account_b.creature.as_ref().unwrap().clone();
445                        let player_b_stats = account_b.stats.clone();
446                        let player_b_health = player_b_creature.health();
447                        account_b.battle = Some(key);
448                        self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
449
450                        // Add battle to state
451                        let expiry = self
452                            .seed
453                            .view
454                            .checked_add(MOVE_EXPIRY)
455                            .expect("view overflow");
456                        let value = Value::Battle {
457                            expiry,
458                            round: 0,
459                            player_a: (*player_a).clone(),
460                            player_a_max_health: player_a_health,
461                            player_a_health,
462                            player_a_pending: None,
463                            player_a_move_counts: [0; TOTAL_MOVES],
464                            player_b: (*player_b).clone(),
465                            player_b_max_health: player_b_health,
466                            player_b_health,
467                            player_b_pending: None,
468                            player_b_move_counts: [0; TOTAL_MOVES],
469                        };
470                        self.insert(Key::Battle(key), value);
471                        events.push(Event::Matched {
472                            battle: key,
473                            expiry,
474                            player_a: (*player_a).clone(),
475                            player_a_creature,
476                            player_a_stats,
477                            player_b: (*player_b).clone(),
478                            player_b_creature,
479                            player_b_stats,
480                        });
481                    }
482
483                    // If there are any remaining trainers, add them to the lobby for the next matching
484                    let remainder = players.chunks_exact(2).remainder();
485                    let mut new_players = BTreeSet::new();
486                    if !remainder.is_empty() {
487                        for player in remainder {
488                            new_players.insert((*player).clone());
489                        }
490                    }
491
492                    // Start lobby as soon as possible
493                    let lobby = Value::Lobby {
494                        expiry: self
495                            .seed
496                            .view
497                            .checked_add(LOBBY_EXPIRY)
498                            .expect("view overflow"),
499                        players: new_players,
500                    };
501                    self.insert(Key::Lobby, lobby);
502                } else {
503                    // Update existing lobby with new trainer
504                    let lobby = Value::Lobby { expiry, players };
505                    self.insert(Key::Lobby, lobby);
506                }
507            }
508            Instruction::Move(encrypted_move) => {
509                // If not in a battle, not valid
510                let Some(battle) = account.battle else {
511                    return events;
512                };
513
514                // If the player has not yet moved, store the move
515                let Some(Value::Battle {
516                    expiry,
517                    round,
518                    player_a,
519                    player_a_max_health,
520                    player_a_health,
521                    mut player_a_pending,
522                    player_a_move_counts,
523                    player_b,
524                    player_b_max_health,
525                    player_b_health,
526                    mut player_b_pending,
527                    player_b_move_counts,
528                }) = self.get(&Key::Battle(battle)).await
529                else {
530                    panic!("Battle should exist");
531                };
532
533                // If the turn has expired, not valid
534                if expiry < self.seed.view {
535                    return events;
536                }
537
538                // Store the move (ok to update encrypted value)
539                if player_a == transaction.public && player_a_pending.is_none() {
540                    player_a_pending = Some(encrypted_move.clone());
541                } else if player_b == transaction.public && player_b_pending.is_none() {
542                    player_b_pending = Some(encrypted_move.clone());
543                } else {
544                    return events;
545                }
546
547                // Emit event
548                events.push(Event::Locked {
549                    battle,
550                    round,
551                    locker: transaction.public.clone(),
552                    observer: if player_a == transaction.public {
553                        player_b.clone()
554                    } else {
555                        player_a.clone()
556                    },
557                    ciphertext: encrypted_move.clone(),
558                });
559
560                // Store update in battle
561                let value = Value::Battle {
562                    expiry,
563                    round,
564                    player_a,
565                    player_a_max_health,
566                    player_a_health,
567                    player_a_pending,
568                    player_a_move_counts,
569                    player_b,
570                    player_b_max_health,
571                    player_b_health,
572                    player_b_pending,
573                    player_b_move_counts,
574                };
575                self.insert(Key::Battle(battle), value);
576            }
577            Instruction::Settle(signature) => {
578                // If not in a battle, return false
579                let Some(battle) = account.battle else {
580                    return events; // Not in battle, no-op
581                };
582
583                // Get battle
584                let Some(Value::Battle {
585                    expiry,
586                    mut round,
587                    player_a,
588                    player_a_max_health,
589                    mut player_a_health,
590                    player_a_pending,
591                    mut player_a_move_counts,
592                    player_b,
593                    player_b_max_health,
594                    mut player_b_health,
595                    player_b_pending,
596                    mut player_b_move_counts,
597                }) = self.get(&Key::Battle(battle)).await
598                else {
599                    panic!("Battle should exist");
600                };
601
602                // If turn has not expired, not valid
603                if expiry > self.seed.view {
604                    return events;
605                }
606
607                // If the signature is not valid, return false
608                let seed = Seed::new(expiry, *signature);
609                if seed != self.seed {
610                    match self.precomputations.get(&Task::Seed(seed.clone())) {
611                        Some(TaskResult::Seed(result)) => {
612                            if !result {
613                                return events;
614                            }
615                        }
616                        None => {
617                            if !seed.verify(&self.namespace, &self.master) {
618                                return events; // Invalid signature, no-op
619                            }
620                        }
621                        _ => unreachable!(),
622                    }
623                }
624
625                // Decrypt player A move
626                let mut player_a_move = if let Some(player_a_pending) = player_a_pending {
627                    // Use the signature that was verified for the battle expiry
628                    match self
629                        .precomputations
630                        .get(&Task::Decrypt(seed.clone(), player_a_pending.clone()))
631                    {
632                        Some(TaskResult::Decrypt(result)) => result[0],
633                        None => {
634                            let raw: [u8; 32] = decrypt::<MinSig>(signature, &player_a_pending)
635                                .map(|block| block.as_ref().try_into().unwrap())
636                                .unwrap_or_else(|| [0; 32]);
637                            raw[0]
638                        }
639                        _ => unreachable!(),
640                    }
641                } else {
642                    // If no move, return 0
643                    0
644                };
645                if player_a_move >= TOTAL_MOVES as u8 {
646                    player_a_move = 0;
647                }
648
649                // Get player A creature
650                let Some(Value::Account(account)) = self.get(&Key::Account(player_a.clone())).await
651                else {
652                    panic!("Player A should have an account");
653                };
654                let player_a_creature = account.creature.as_ref().unwrap();
655
656                // Check move usage limit for player A
657                let player_a_limits = player_a_creature.get_move_usage_limits();
658                if player_a_move_counts[player_a_move as usize]
659                    >= player_a_limits[player_a_move as usize]
660                {
661                    player_a_move = 0;
662                }
663
664                // Apply move
665                let (player_a_defense, player_a_power) =
666                    player_a_creature.action(player_a_move, self.seed.signature);
667
668                // Decrypt player B move
669                let mut player_b_move = if let Some(player_b_pending) = player_b_pending {
670                    // If can't decrypt, return 0
671                    match self
672                        .precomputations
673                        .get(&Task::Decrypt(seed, player_b_pending.clone()))
674                    {
675                        Some(TaskResult::Decrypt(result)) => result[0],
676                        None => {
677                            let raw: [u8; 32] = decrypt::<MinSig>(signature, &player_b_pending)
678                                .map(|block| block.as_ref().try_into().unwrap())
679                                .unwrap_or_else(|| [0; 32]);
680                            raw[0]
681                        }
682                        _ => unreachable!(),
683                    }
684                } else {
685                    // If no move, return 0
686                    0
687                };
688                if player_b_move >= TOTAL_MOVES as u8 {
689                    player_b_move = 0;
690                }
691
692                // Get player B creature
693                let Some(Value::Account(account)) = self.get(&Key::Account(player_b.clone())).await
694                else {
695                    panic!("Player B should have an account");
696                };
697                let player_b_creature = account.creature.as_ref().unwrap();
698
699                // Check move usage limit for player B
700                let player_b_limits = player_b_creature.get_move_usage_limits();
701                if player_b_move_counts[player_b_move as usize]
702                    >= player_b_limits[player_b_move as usize]
703                {
704                    player_b_move = 0;
705                }
706
707                // Apply move
708                let (player_b_defense, player_b_power) =
709                    player_b_creature.action(player_b_move, self.seed.signature);
710
711                // Apply impact
712                // Track effective health (can go negative) for overkill calculation
713                let mut player_a_effective_health = player_a_health as i16;
714                let mut player_b_effective_health = player_b_health as i16;
715
716                if player_a_defense && !player_b_defense {
717                    // Player A restores health, then takes damage
718                    player_a_health = player_a_health
719                        .saturating_add(player_a_power)
720                        .min(player_a_max_health);
721                    player_a_effective_health = (player_a_health as i16) - (player_b_power as i16);
722                    player_a_health = player_a_health.saturating_sub(player_b_power);
723                } else if !player_a_defense && player_b_defense {
724                    // Player B restores health, then takes damage
725                    player_b_health = player_b_health
726                        .saturating_add(player_b_power)
727                        .min(player_b_max_health);
728                    player_b_effective_health = (player_b_health as i16) - (player_a_power as i16);
729                    player_b_health = player_b_health.saturating_sub(player_a_power);
730                } else if player_a_defense && player_b_defense {
731                    // Player A and B restore health
732                    player_a_health = player_a_health
733                        .saturating_add(player_a_power)
734                        .min(player_a_max_health);
735                    player_b_health = player_b_health
736                        .saturating_add(player_b_power)
737                        .min(player_b_max_health);
738                    player_a_effective_health = player_a_health as i16;
739                    player_b_effective_health = player_b_health as i16;
740                } else {
741                    // Player A and B take damage
742                    player_a_effective_health = (player_a_health as i16) - (player_b_power as i16);
743                    player_b_effective_health = (player_b_health as i16) - (player_a_power as i16);
744                    player_a_health = player_a_health.saturating_sub(player_b_power);
745                    player_b_health = player_b_health.saturating_sub(player_a_power);
746                }
747                // Increment move counts
748                //
749                // We should timeout before we ever overflow but might as well be defensive.
750                player_a_move_counts[player_a_move as usize] =
751                    player_a_move_counts[player_a_move as usize].saturating_add(1);
752                player_b_move_counts[player_b_move as usize] =
753                    player_b_move_counts[player_b_move as usize].saturating_add(1);
754
755                // Increment round counter
756                round += 1;
757
758                // Compute next expiry
759                let next_expiry = self
760                    .seed
761                    .view
762                    .checked_add(MOVE_EXPIRY)
763                    .expect("view overflow");
764                events.push(Event::Moved {
765                    battle,
766                    round,
767                    expiry: next_expiry,
768                    player_a: player_a.clone(),
769                    player_a_health,
770                    player_a_move,
771                    player_a_move_counts,
772                    player_a_power,
773                    player_b: player_b.clone(),
774                    player_b_health,
775                    player_b_move,
776                    player_b_move_counts,
777                    player_b_power,
778                });
779
780                // Determine whether the battle is over
781                if player_a_health == 0 && player_b_health > 0 {
782                    // Player B wins
783                    self.delete(Key::Battle(battle));
784
785                    // Get original stats
786                    let Some(Value::Account(mut account_a)) =
787                        self.get(&Key::Account(player_a.clone())).await
788                    else {
789                        panic!("Player A should have an account");
790                    };
791                    let old_account_a_stats = account_a.stats.clone();
792                    let Some(Value::Account(mut account_b)) =
793                        self.get(&Key::Account(player_b.clone())).await
794                    else {
795                        panic!("Player B should have an account");
796                    };
797                    let old_account_b_stats = account_b.stats.clone();
798
799                    // Update losses
800                    account_a.stats.losses = account_a.stats.losses.saturating_add(1);
801                    account_a.battle = None;
802
803                    // Update wins
804                    account_b.stats.wins = account_b.stats.wins.saturating_add(1);
805                    account_b.battle = None;
806
807                    // Update ELO scores (player A has 0 or negative health, player B has some)
808                    let max_health_a = account_a.creature.as_ref().unwrap().health();
809                    let max_health_b = account_b.creature.as_ref().unwrap().health();
810                    let (new_elo_a, new_elo_b) = elo::update(
811                        account_a.stats.elo,
812                        player_a_effective_health,
813                        max_health_a,
814                        account_b.stats.elo,
815                        player_b_effective_health,
816                        max_health_b,
817                    );
818                    account_a.stats.elo = new_elo_a;
819                    let new_account_a_stats = account_a.stats.clone();
820                    account_b.stats.elo = new_elo_b;
821                    let new_account_b_stats = account_b.stats.clone();
822                    self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
823                    self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
824
825                    // Update leaderboard
826                    let mut leaderboard = match self.get(&Key::Leaderboard).await {
827                        Some(Value::Leaderboard(lb)) => lb,
828                        _ => Leaderboard::default(),
829                    };
830                    leaderboard.update(player_a.clone(), new_account_a_stats.clone());
831                    leaderboard.update(player_b.clone(), new_account_b_stats.clone());
832                    self.insert(Key::Leaderboard, Value::Leaderboard(leaderboard.clone()));
833
834                    // Add event
835                    events.push(Event::Settled {
836                        battle,
837                        round,
838                        player_a,
839                        player_a_old: old_account_a_stats,
840                        player_a_new: new_account_a_stats,
841                        player_b,
842                        player_b_old: old_account_b_stats,
843                        player_b_new: new_account_b_stats,
844                        outcome: Outcome::PlayerB,
845                        leaderboard,
846                    });
847                } else if player_b_health == 0 && player_a_health > 0 {
848                    // Player A wins
849                    self.delete(Key::Battle(battle));
850
851                    // Get original stats
852                    let Some(Value::Account(mut account_a)) =
853                        self.get(&Key::Account(player_a.clone())).await
854                    else {
855                        panic!("Player A should have an account");
856                    };
857                    let old_account_a_stats = account_a.stats.clone();
858                    let Some(Value::Account(mut account_b)) =
859                        self.get(&Key::Account(player_b.clone())).await
860                    else {
861                        panic!("Player B should have an account");
862                    };
863                    let old_account_b_stats = account_b.stats.clone();
864
865                    // Update losses
866                    account_b.stats.losses = account_b.stats.losses.saturating_add(1);
867                    account_b.battle = None;
868
869                    // Update wins
870                    account_a.stats.wins = account_a.stats.wins.saturating_add(1);
871                    account_a.battle = None;
872
873                    // Update ELO scores (player B has 0 or negative health, player A has some)
874                    let max_health_a = account_a.creature.as_ref().unwrap().health();
875                    let max_health_b = account_b.creature.as_ref().unwrap().health();
876                    let (new_elo_a, new_elo_b) = elo::update(
877                        account_a.stats.elo,
878                        player_a_effective_health,
879                        max_health_a,
880                        account_b.stats.elo,
881                        player_b_effective_health,
882                        max_health_b,
883                    );
884                    account_a.stats.elo = new_elo_a;
885                    let new_account_a_stats = account_a.stats.clone();
886                    account_b.stats.elo = new_elo_b;
887                    let new_account_b_stats = account_b.stats.clone();
888                    self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
889                    self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
890
891                    // Update leaderboard
892                    let mut leaderboard = match self.get(&Key::Leaderboard).await {
893                        Some(Value::Leaderboard(lb)) => lb,
894                        _ => Leaderboard::default(),
895                    };
896                    leaderboard.update(player_a.clone(), new_account_a_stats.clone());
897                    leaderboard.update(player_b.clone(), new_account_b_stats.clone());
898                    self.insert(Key::Leaderboard, Value::Leaderboard(leaderboard.clone()));
899
900                    // Add event
901                    events.push(Event::Settled {
902                        battle,
903                        round,
904                        player_a,
905                        player_a_old: old_account_a_stats,
906                        player_a_new: new_account_a_stats,
907                        player_b,
908                        player_b_old: old_account_b_stats,
909                        player_b_new: new_account_b_stats,
910                        outcome: Outcome::PlayerA,
911                        leaderboard,
912                    });
913                } else if round >= MAX_BATTLE_ROUNDS
914                    || (player_a_health == 0 && player_b_health == 0)
915                {
916                    // Draw
917                    self.delete(Key::Battle(battle));
918
919                    // Get original stats
920                    let Some(Value::Account(mut account_a)) =
921                        self.get(&Key::Account(player_a.clone())).await
922                    else {
923                        panic!("Player A should have an account");
924                    };
925                    let old_account_a_stats = account_a.stats.clone();
926                    let Some(Value::Account(mut account_b)) =
927                        self.get(&Key::Account(player_b.clone())).await
928                    else {
929                        panic!("Player B should have an account");
930                    };
931                    let old_account_b_stats = account_b.stats.clone();
932
933                    // Update draws
934                    account_a.stats.draws = account_a.stats.draws.saturating_add(1);
935                    account_a.battle = None;
936
937                    // Update draws
938                    account_b.stats.draws = account_b.stats.draws.saturating_add(1);
939                    account_b.battle = None;
940
941                    // Update ELO scores based on effective health (including overkill)
942                    let max_health_a = account_a.creature.as_ref().unwrap().health();
943                    let max_health_b = account_b.creature.as_ref().unwrap().health();
944                    let (new_elo_a, new_elo_b) = elo::update(
945                        account_a.stats.elo,
946                        player_a_effective_health,
947                        max_health_a,
948                        account_b.stats.elo,
949                        player_b_effective_health,
950                        max_health_b,
951                    );
952                    account_a.stats.elo = new_elo_a;
953                    let new_account_a_stats = account_a.stats.clone();
954                    account_b.stats.elo = new_elo_b;
955                    let new_account_b_stats = account_b.stats.clone();
956                    self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
957                    self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
958
959                    // Update leaderboard
960                    let mut leaderboard = match self.get(&Key::Leaderboard).await {
961                        Some(Value::Leaderboard(lb)) => lb,
962                        _ => Leaderboard::default(),
963                    };
964                    leaderboard.update(player_a.clone(), new_account_a_stats.clone());
965                    leaderboard.update(player_b.clone(), new_account_b_stats.clone());
966                    self.insert(Key::Leaderboard, Value::Leaderboard(leaderboard.clone()));
967
968                    // Add event
969                    events.push(Event::Settled {
970                        battle,
971                        round,
972                        player_a,
973                        player_a_old: old_account_a_stats,
974                        player_a_new: new_account_a_stats,
975                        player_b,
976                        player_b_old: old_account_b_stats,
977                        player_b_new: new_account_b_stats,
978                        outcome: Outcome::Draw,
979                        leaderboard,
980                    });
981                } else {
982                    // Battle continues
983                    self.insert(
984                        Key::Battle(battle),
985                        Value::Battle {
986                            expiry: next_expiry,
987                            round,
988                            player_a,
989                            player_a_max_health,
990                            player_a_health,
991                            player_a_pending: None,
992                            player_a_move_counts,
993                            player_b,
994                            player_b_max_health,
995                            player_b_health,
996                            player_b_pending: None,
997                            player_b_move_counts,
998                        },
999                    );
1000                }
1001            }
1002        }
1003
1004        events
1005    }
1006
1007    pub async fn execute(
1008        &mut self,
1009        #[cfg(feature = "parallel")] pool: ThreadPool,
1010        transactions: Vec<Transaction>,
1011    ) -> (Vec<Output>, BTreeMap<PublicKey, u64>) {
1012        // Iterate over all transactions with valid nonces (applying in the process) and extract expensive cryptographic operations
1013        let mut processed_nonces = BTreeMap::new();
1014        let mut seed_ops = HashSet::new();
1015        let mut decrypt_ops = HashSet::new();
1016        let mut valid_transactions = Vec::new();
1017        for tx in transactions {
1018            // Must be applied in order to ensure blocks with multiple transactions from same
1019            // account are handled properly.
1020            if !self.prepare(&tx).await {
1021                continue;
1022            }
1023
1024            // Track the next nonce for this public key
1025            processed_nonces.insert(tx.public.clone(), tx.nonce.saturating_add(1));
1026
1027            // Extract operations
1028            let ops = self.extract(&tx).await;
1029            for op in ops {
1030                match op {
1031                    Task::Seed(_) => seed_ops.insert(op),
1032                    Task::Decrypt(_, _) => decrypt_ops.insert(op),
1033                };
1034            }
1035            valid_transactions.push(tx);
1036        }
1037
1038        // Execute operations
1039        macro_rules! process_ops {
1040            ($iter:ident) => {{
1041                // Verify all seeds
1042                let mut results: HashMap<Task, TaskResult> = seed_ops
1043                    .$iter()
1044                    .map(|op| match op {
1045                        Task::Seed(ref seed) => {
1046                            if self.seed == *seed {
1047                                return (op, TaskResult::Seed(true));
1048                            }
1049                            let result = seed.verify(&self.namespace, &self.master);
1050                            (op, TaskResult::Seed(result))
1051                        }
1052                        _ => unreachable!(),
1053                    })
1054                    .collect();
1055
1056                // Only decrypt for valid signatures
1057                let decrypt_results: HashMap<Task, TaskResult> = decrypt_ops
1058                    .$iter()
1059                    .flat_map(|op| match op {
1060                        Task::Decrypt(ref seed, ref ciphertext) => {
1061                            // If seed is invalid, skip decryption (decryption won't be needed)
1062                            if !matches!(
1063                                results.get(&Task::Seed(seed.clone())).unwrap(), // we should never be missing a seed
1064                                TaskResult::Seed(true)
1065                            ) {
1066                                return None;
1067                            }
1068
1069                            // If seed is valid, decrypt
1070                            let result = decrypt::<MinSig>(&seed.signature, ciphertext)
1071                                .map(|block| block.as_ref().try_into().unwrap())
1072                                .unwrap_or_else(|| [0; 32]);
1073                            Some((op, TaskResult::Decrypt(result)))
1074                        }
1075                        _ => unreachable!(),
1076                    })
1077                    .collect();
1078
1079                // Merge results
1080                results.extend(decrypt_results);
1081                results
1082            }};
1083        }
1084        #[cfg(feature = "parallel")]
1085        let precomputations = pool.install(|| process_ops!(into_par_iter));
1086        #[cfg(not(feature = "parallel"))]
1087        let precomputations = process_ops!(into_iter);
1088
1089        // Store precomputations
1090        self.precomputations = precomputations;
1091
1092        // Apply transactions (using cached operation results)
1093        let mut events = Vec::new();
1094        for tx in valid_transactions {
1095            events.extend(self.apply(&tx).await.into_iter().map(Output::Event));
1096            events.push(Output::Transaction(tx));
1097        }
1098
1099        (events, processed_nonces)
1100    }
1101
1102    pub fn commit(self) -> Vec<(Key, Status)> {
1103        self.pending.into_iter().collect()
1104    }
1105}
1106
1107impl<'a, S: State> State for Layer<'a, S> {
1108    async fn get(&self, key: &Key) -> Option<Value> {
1109        match self.pending.get(key) {
1110            Some(Status::Update(value)) => Some(value.clone()),
1111            Some(Status::Delete) => None,
1112            None => self.state.get(key).await,
1113        }
1114    }
1115
1116    async fn insert(&mut self, key: Key, value: Value) {
1117        self.pending.insert(key, Status::Update(value));
1118    }
1119
1120    async fn delete(&mut self, key: &Key) {
1121        self.pending.insert(key.clone(), Status::Delete);
1122    }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127    use super::*;
1128    use commonware_cryptography::bls12381::tle::{encrypt, Block, Ciphertext};
1129    use commonware_cryptography::{
1130        bls12381::primitives::{ops, variant::MinSig},
1131        ed25519, PrivateKeyExt, Signer,
1132    };
1133    use commonware_runtime::deterministic::Runner;
1134    use commonware_runtime::Runner as _;
1135    use rand::{rngs::StdRng, SeedableRng};
1136    use std::collections::HashMap;
1137
1138    const TEST_NAMESPACE: &[u8] = b"test-namespace";
1139
1140    struct MockState {
1141        data: HashMap<Key, Value>,
1142    }
1143
1144    impl MockState {
1145        fn new() -> Self {
1146            Self {
1147                data: HashMap::new(),
1148            }
1149        }
1150    }
1151
1152    impl State for MockState {
1153        async fn get(&self, key: &Key) -> Option<Value> {
1154            self.data.get(key).cloned()
1155        }
1156
1157        async fn insert(&mut self, key: Key, value: Value) {
1158            self.data.insert(key, value);
1159        }
1160
1161        async fn delete(&mut self, key: &Key) {
1162            self.data.remove(key);
1163        }
1164    }
1165
1166    // Master keypair for all timelock operations in tests
1167    fn create_network_keypair() -> (
1168        commonware_cryptography::bls12381::primitives::group::Private,
1169        <MinSig as commonware_cryptography::bls12381::primitives::variant::Variant>::Public,
1170    ) {
1171        let mut rng = StdRng::seed_from_u64(0);
1172        ops::keypair::<_, MinSig>(&mut rng)
1173    }
1174
1175    fn create_seed(
1176        network_secret: &commonware_cryptography::bls12381::primitives::group::Private,
1177        view: u64,
1178    ) -> Seed {
1179        use commonware_consensus::threshold_simplex::types::{seed_namespace, view_message};
1180        let seed_namespace = seed_namespace(TEST_NAMESPACE);
1181        let message = view_message(view);
1182        Seed::new(
1183            view,
1184            ops::sign_message::<MinSig>(network_secret, Some(&seed_namespace), &message),
1185        )
1186    }
1187
1188    fn create_test_move_ciphertext(
1189        master_public: <MinSig as commonware_cryptography::bls12381::primitives::variant::Variant>::Public,
1190        next_expiry: u64,
1191        move_data: u8,
1192    ) -> Ciphertext<MinSig> {
1193        use commonware_consensus::threshold_simplex::types::{seed_namespace, view_message};
1194
1195        // Target needs to match what the seed signature signs over
1196        let seed_namespace = seed_namespace(TEST_NAMESPACE);
1197        let view_msg = view_message(next_expiry);
1198
1199        // Create a 32-byte move message with the move data
1200        let mut message = [0u8; 32];
1201        message[0] = move_data; // First byte is the actual move
1202
1203        let mut rng = StdRng::seed_from_u64(42); // Different seed for encryption randomness
1204        encrypt::<_, MinSig>(
1205            &mut rng,
1206            master_public,
1207            (Some(&seed_namespace), &view_msg),
1208            &Block::new(message),
1209        )
1210    }
1211
1212    #[allow(dead_code)]
1213    fn decrypt_test_move(
1214        network_secret: &commonware_cryptography::bls12381::primitives::group::Private,
1215        next_expiry: u64,
1216        ciphertext: &Ciphertext<MinSig>,
1217    ) -> Option<u8> {
1218        use commonware_cryptography::bls12381::tle::decrypt;
1219
1220        let seed = create_seed(network_secret, next_expiry);
1221        decrypt::<MinSig>(&seed.signature, ciphertext).map(|block| block.as_ref()[0])
1222    }
1223
1224    fn create_test_actor(seed: u64) -> (ed25519::PrivateKey, ed25519::PublicKey) {
1225        let private = ed25519::PrivateKey::from_seed(seed);
1226        let public = private.public_key();
1227        (private, public)
1228    }
1229
1230    #[test]
1231    fn test_invalid_nonce_dropped() {
1232        let executor = Runner::default();
1233        executor.start(|_| async move {
1234            let state = MockState::new();
1235            let (network_secret, master_public) = create_network_keypair();
1236            let seed = create_seed(&network_secret, 1);
1237            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1238
1239            let (signer, _) = create_test_actor(1);
1240
1241            // Try to prepare with wrong nonce (should fail)
1242            let tx = Transaction::sign(&signer, 1, Instruction::Generate);
1243            assert!(!layer.prepare(&tx).await);
1244
1245            // Prepare with correct nonce
1246            let tx = Transaction::sign(&signer, 0, Instruction::Generate);
1247            assert!(layer.prepare(&tx).await);
1248
1249            // The nonce should now be 1 in the pending state
1250            // Try to use old nonce again in the same layer (should fail)
1251            let tx = Transaction::sign(&signer, 0, Instruction::Generate);
1252            assert!(!layer.prepare(&tx).await);
1253
1254            // Should succeed with new nonce
1255            let tx = Transaction::sign(&signer, 1, Instruction::Generate);
1256            assert!(layer.prepare(&tx).await);
1257
1258            // Consume the layer at the end of the test
1259            let _ = layer.commit();
1260        });
1261    }
1262
1263    #[test]
1264    fn test_must_have_creature_before_battle() {
1265        let executor = Runner::default();
1266        executor.start(|_| async move {
1267            let state = MockState::new();
1268            let (network_secret, master_public) = create_network_keypair();
1269            let seed = create_seed(&network_secret, 1);
1270            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1271
1272            let (signer, _) = create_test_actor(1);
1273
1274            // Prepare account
1275            let tx = Transaction::sign(&signer, 0, Instruction::Match);
1276            assert!(layer.prepare(&tx).await);
1277
1278            // Try to match without creature
1279            let events = layer.apply(&tx).await;
1280            assert!(events.is_empty()); // Should produce no events
1281
1282            // Generate creature first
1283            let tx = Transaction::sign(&signer, 1, Instruction::Generate);
1284            assert!(layer.prepare(&tx).await);
1285            let events = layer.apply(&tx).await;
1286            assert_eq!(events.len(), 1);
1287            assert!(matches!(events[0], Event::Generated { .. }));
1288
1289            // Now matching should work (adds to lobby)
1290            // Continue with the same layer - nonce is already incremented from prepare
1291            let tx = Transaction::sign(&signer, 2, Instruction::Match);
1292            assert!(layer.prepare(&tx).await);
1293            let events = layer.apply(&tx).await;
1294            assert!(events.is_empty()); // Just adds to lobby, no match yet
1295
1296            // Consume the layer at the end of the test
1297            let _ = layer.commit();
1298        });
1299    }
1300
1301    #[test]
1302    fn test_cannot_enter_multiple_concurrent_battles() {
1303        let executor = Runner::default();
1304        executor.start(|_| async move {
1305            let mut state = MockState::new();
1306            let (network_secret, master_public) = create_network_keypair();
1307            let seed = create_seed(&network_secret, 1);
1308            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1309
1310            let (signer_a, _) = create_test_actor(1);
1311            let (signer_b, _) = create_test_actor(2);
1312
1313            // Prepare accounts and generate creatures
1314            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1315            assert!(layer.prepare(&tx).await);
1316            layer.apply(&tx).await;
1317
1318            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1319            assert!(layer.prepare(&tx).await);
1320            layer.apply(&tx).await;
1321
1322            // Match both players - first player creates lobby
1323            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1324            assert!(layer.prepare(&tx).await);
1325            layer.apply(&tx).await;
1326
1327            // Commit and create new layer with advanced view to expire the lobby
1328            let changes = layer.commit();
1329            state.apply(changes).await;
1330            let new_seed = create_seed(&network_secret, 102);
1331            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1332
1333            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1334            assert!(layer.prepare(&tx).await);
1335            let events = layer.apply(&tx).await;
1336            assert_eq!(events.len(), 1);
1337            assert!(matches!(events[0], Event::Matched { .. }));
1338
1339            // Try to match again while in battle (should fail)
1340            let tx = Transaction::sign(&signer_a, 2, Instruction::Match);
1341            assert!(layer.prepare(&tx).await);
1342            let events = layer.apply(&tx).await;
1343            assert!(events.is_empty()); // Should not be able to match again
1344
1345            // Consume the layer at the end of the test
1346            let _ = layer.commit();
1347        });
1348    }
1349
1350    #[test]
1351    fn test_can_only_swap_creatures_when_not_in_battle() {
1352        let executor = Runner::default();
1353        executor.start(|_| async move {
1354            let mut state = MockState::new();
1355            let (network_secret, master_public) = create_network_keypair();
1356            let seed = create_seed(&network_secret, 1);
1357            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1358
1359            let (signer_a, _) = create_test_actor(1);
1360            let (signer_b, _) = create_test_actor(2);
1361
1362            // Prepare and generate creature for actor A
1363            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1364            assert!(layer.prepare(&tx).await);
1365            layer.apply(&tx).await;
1366
1367            // Can generate new creature when not in battle
1368            let tx = Transaction::sign(&signer_a, 1, Instruction::Generate);
1369            assert!(layer.prepare(&tx).await);
1370            let events = layer.apply(&tx).await;
1371            assert_eq!(events.len(), 1);
1372            assert!(matches!(events[0], Event::Generated { .. }));
1373
1374            // Prepare actor B and match them
1375            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1376            assert!(layer.prepare(&tx).await);
1377            layer.apply(&tx).await;
1378
1379            let tx = Transaction::sign(&signer_a, 2, Instruction::Match);
1380            assert!(layer.prepare(&tx).await);
1381            layer.apply(&tx).await;
1382
1383            // Commit and create new layer with advanced view to expire the lobby
1384            let changes = layer.commit();
1385            state.apply(changes).await;
1386            let new_seed = create_seed(&network_secret, 102);
1387            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1388
1389            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1390            assert!(layer.prepare(&tx).await);
1391            let events = layer.apply(&tx).await;
1392            assert_eq!(events.len(), 1);
1393            assert!(matches!(events[0], Event::Matched { .. }));
1394
1395            // Try to generate new creature while in battle (should fail)
1396            let tx = Transaction::sign(&signer_a, 3, Instruction::Generate);
1397            assert!(layer.prepare(&tx).await);
1398            let events = layer.apply(&tx).await;
1399            assert!(events.is_empty());
1400
1401            // Consume the layer at the end of the test
1402            let _ = layer.commit();
1403        });
1404    }
1405
1406    #[test]
1407    fn test_cannot_send_multiple_moves_in_single_round() {
1408        let executor = Runner::default();
1409        executor.start(|_| async move {
1410            let mut state = MockState::new();
1411            let (network_secret, master_public) = create_network_keypair();
1412            let seed = create_seed(&network_secret, 1);
1413            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1414
1415            let (signer_a, _) = create_test_actor(1);
1416            let (signer_b, _) = create_test_actor(2);
1417
1418            // Setup battle
1419            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1420            assert!(layer.prepare(&tx).await);
1421            layer.apply(&tx).await;
1422
1423            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1424            assert!(layer.prepare(&tx).await);
1425            layer.apply(&tx).await;
1426
1427            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1428            assert!(layer.prepare(&tx).await);
1429            layer.apply(&tx).await;
1430
1431            // Commit and create new layer with advanced view to expire the lobby
1432            let changes = layer.commit();
1433            state.apply(changes).await;
1434            let new_seed = create_seed(&network_secret, 102);
1435            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1436
1437            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1438            assert!(layer.prepare(&tx).await);
1439            let events = layer.apply(&tx).await;
1440            assert_eq!(events.len(), 1);
1441
1442            // Send first move - use the battle expiry as the timelock target
1443            let battle_expiry = layer
1444                .view()
1445                .checked_add(MOVE_EXPIRY)
1446                .expect("view overflow");
1447            let move1 = create_test_move_ciphertext(master_public, battle_expiry, 1);
1448            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move1.clone()));
1449            assert!(layer.prepare(&tx).await);
1450            layer.apply(&tx).await;
1451
1452            // Try to send second move in same round (should fail)
1453            let move2 = create_test_move_ciphertext(master_public, battle_expiry, 2);
1454            let tx = Transaction::sign(&signer_a, 3, Instruction::Move(move2));
1455            assert!(layer.prepare(&tx).await);
1456            let events = layer.apply(&tx).await;
1457            assert!(events.is_empty());
1458
1459            // Consume the layer at the end of the test
1460            let _ = layer.commit();
1461        });
1462    }
1463
1464    #[test]
1465    fn test_one_player_can_win_if_other_doesnt_play() {
1466        let executor = Runner::default();
1467        executor.start(|_| async move {
1468            let mut state = MockState::new();
1469            let (network_secret, master_public) = create_network_keypair();
1470            let seed = create_seed(&network_secret, 1);
1471            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1472
1473            let (signer_a, _) = create_test_actor(1);
1474            let (signer_b, _) = create_test_actor(2);
1475
1476            // Setup battle
1477            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1478            assert!(layer.prepare(&tx).await);
1479            layer.apply(&tx).await;
1480
1481            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1482            assert!(layer.prepare(&tx).await);
1483            layer.apply(&tx).await;
1484
1485            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1486            assert!(layer.prepare(&tx).await);
1487            layer.apply(&tx).await;
1488
1489            // Commit and create new layer with advanced view to expire the lobby
1490            let changes = layer.commit();
1491            state.apply(changes).await;
1492            let new_seed = create_seed(&network_secret, 102);
1493            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1494
1495            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1496            assert!(layer.prepare(&tx).await);
1497            let events = layer.apply(&tx).await;
1498            assert_eq!(events.len(), 1);
1499
1500            // Get the battle expiry
1501            let battle_expiry = layer
1502                .view()
1503                .checked_add(MOVE_EXPIRY)
1504                .expect("view overflow");
1505
1506            // Only player A sends a move (offensive move)
1507            let move_a = create_test_move_ciphertext(master_public, battle_expiry, 2);
1508            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
1509            assert!(layer.prepare(&tx).await);
1510            layer.apply(&tx).await;
1511
1512            // Commit and create new layer with advanced view past expiry
1513            let changes = layer.commit();
1514            state.apply(changes).await;
1515            let new_seed = create_seed(&network_secret, battle_expiry + 1);
1516            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1517
1518            let signature = create_seed(&network_secret, battle_expiry);
1519            let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
1520            assert!(layer.prepare(&tx).await);
1521            let events = layer.apply(&tx).await;
1522
1523            // Should have a move event (player B defaults to no move)
1524            assert!(events.iter().any(|e| matches!(e, Event::Moved { .. })));
1525
1526            // Continue playing until someone wins
1527            // Player B still doesn't play, so eventually they should lose
1528
1529            // Consume the layer at the end of the test
1530            let _ = layer.commit();
1531        });
1532    }
1533
1534    #[test]
1535    fn test_scores_update_correctly_on_game_over() {
1536        let executor = Runner::default();
1537        executor.start(|_| async move {
1538            let mut state = MockState::new();
1539            let (network_secret, master_public) = create_network_keypair();
1540            let seed = create_seed(&network_secret, 1);
1541            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1542
1543            let (signer_a, actor_a) = create_test_actor(1);
1544            let (signer_b, actor_b) = create_test_actor(2);
1545
1546            // Setup battle
1547            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1548            assert!(layer.prepare(&tx).await);
1549            layer.apply(&tx).await;
1550
1551            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1552            assert!(layer.prepare(&tx).await);
1553            layer.apply(&tx).await;
1554
1555            // Get initial account states
1556            let account_a_initial = layer.get(&Key::Account(actor_a.clone())).await.unwrap();
1557            let account_b_initial = layer.get(&Key::Account(actor_b.clone())).await.unwrap();
1558
1559            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1560            assert!(layer.prepare(&tx).await);
1561            layer.apply(&tx).await;
1562
1563            // Commit and create new layer with advanced view to expire the lobby
1564            let changes = layer.commit();
1565            state.apply(changes).await;
1566            let new_seed = create_seed(&network_secret, 102);
1567            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1568
1569            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1570            assert!(layer.prepare(&tx).await);
1571            let events = layer.apply(&tx).await;
1572            assert_eq!(events.len(), 1);
1573
1574            let _battle_digest = if let Event::Matched { battle, .. } = &events[0] {
1575                *battle
1576            } else {
1577                panic!("Expected matched event");
1578            };
1579
1580            // Play until someone loses all health
1581            let mut round = 0;
1582            loop {
1583                // Get current battle expiry
1584                let battle_expiry = layer
1585                    .view()
1586                    .checked_add(MOVE_EXPIRY)
1587                    .expect("view overflow");
1588
1589                // Both players make offensive moves
1590                let move_a = create_test_move_ciphertext(master_public, battle_expiry, 3);
1591                let move_b = create_test_move_ciphertext(master_public, battle_expiry, 3);
1592
1593                let tx = Transaction::sign(&signer_a, 2 + round * 2, Instruction::Move(move_a));
1594                assert!(layer.prepare(&tx).await);
1595                layer.apply(&tx).await;
1596
1597                let tx = Transaction::sign(&signer_b, 2 + round, Instruction::Move(move_b));
1598                assert!(layer.prepare(&tx).await);
1599                layer.apply(&tx).await;
1600
1601                // Commit and create new layer with advanced view past expiry
1602                let changes = layer.commit();
1603                state.apply(changes).await;
1604                let new_seed = create_seed(&network_secret, battle_expiry + 1);
1605                layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1606
1607                let signature = create_seed(&network_secret, battle_expiry);
1608                let tx = Transaction::sign(
1609                    &signer_a,
1610                    3 + round * 2,
1611                    Instruction::Settle(signature.signature),
1612                );
1613                assert!(layer.prepare(&tx).await);
1614                let events = layer.apply(&tx).await;
1615
1616                // Check if game ended
1617                if let Some(settled_event) =
1618                    events.iter().find(|e| matches!(e, Event::Settled { .. }))
1619                {
1620                    // Verify scores updated
1621                    let account_a_final = layer.get(&Key::Account(actor_a.clone())).await.unwrap();
1622                    let account_b_final = layer.get(&Key::Account(actor_b.clone())).await.unwrap();
1623
1624                    if let (
1625                        Value::Account(mut acc_a_init),
1626                        Value::Account(mut acc_a_final),
1627                        Value::Account(mut acc_b_init),
1628                        Value::Account(mut acc_b_final),
1629                    ) = (
1630                        account_a_initial.clone(),
1631                        account_a_final,
1632                        account_b_initial.clone(),
1633                        account_b_final,
1634                    ) {
1635                        // Check win/loss/draw counters
1636                        assert!(
1637                            acc_a_final.stats.wins > acc_a_init.stats.wins
1638                                || acc_a_final.stats.losses > acc_a_init.stats.losses
1639                                || acc_a_final.stats.draws > acc_a_init.stats.draws
1640                        );
1641                        assert!(
1642                            acc_b_final.stats.wins > acc_b_init.stats.wins
1643                                || acc_b_final.stats.losses > acc_b_init.stats.losses
1644                                || acc_b_final.stats.draws > acc_b_init.stats.draws
1645                        );
1646
1647                        // Check ELO changed
1648                        assert!(
1649                            acc_a_final.stats.elo != acc_a_init.stats.elo
1650                                || acc_b_final.stats.elo != acc_b_init.stats.elo
1651                        );
1652
1653                        // Battle should be cleared
1654                        assert!(acc_a_final.battle.is_none());
1655                        assert!(acc_b_final.battle.is_none());
1656
1657                        // Verify old/new Elo scores in Settled event are properly populated
1658                        if let Event::Settled {
1659                            player_a,
1660                            player_a_old,
1661                            player_a_new,
1662                            player_b_old,
1663                            player_b_new,
1664                            ..
1665                        } = settled_event
1666                        {
1667                            // Swap results if player A is not the signer
1668                            if player_a != &signer_a.public_key() {
1669                                let acc_temp_init = acc_a_init;
1670                                let acc_temp_final = acc_a_final;
1671                                acc_a_init = acc_b_init;
1672                                acc_a_final = acc_b_final;
1673                                acc_b_init = acc_temp_init;
1674                                acc_b_final = acc_temp_final;
1675                            }
1676
1677                            // Verify old Elo scores match initial account states
1678                            assert_eq!(
1679                                player_a_old.elo, acc_a_init.stats.elo,
1680                                "Player A old Elo should match initial Elo"
1681                            );
1682                            assert_eq!(
1683                                player_b_old.elo, acc_b_init.stats.elo,
1684                                "Player B old Elo should match initial Elo"
1685                            );
1686
1687                            // Verify new Elo scores match final account states
1688                            assert_eq!(
1689                                player_a_new.elo, acc_a_final.stats.elo,
1690                                "Player A new Elo should match final Elo"
1691                            );
1692                            assert_eq!(
1693                                player_b_new.elo, acc_b_final.stats.elo,
1694                                "Player B new Elo should match final Elo"
1695                            );
1696
1697                            // Verify Elo scores actually changed
1698                            assert_ne!(
1699                                player_a_old.elo, player_a_new.elo,
1700                                "Player A Elo should have changed"
1701                            );
1702                            assert_ne!(
1703                                player_b_old.elo, player_b_new.elo,
1704                                "Player B Elo should have changed"
1705                            );
1706                        }
1707                    }
1708                    break;
1709                }
1710
1711                round += 1;
1712                if round > 10 {
1713                    panic!("Game should have ended by now");
1714                }
1715            }
1716
1717            // Consume the layer at the end of the test
1718            let _ = layer.commit();
1719        });
1720    }
1721
1722    #[test]
1723    fn test_invalid_signature_does_nothing() {
1724        let executor = Runner::default();
1725        executor.start(|_| async move {
1726            let mut state = MockState::new();
1727            let (network_secret, master_public) = create_network_keypair();
1728            let seed = create_seed(&network_secret, 1);
1729            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1730
1731            let (signer_a, actor_a) = create_test_actor(1);
1732            let (signer_b, _) = create_test_actor(2);
1733
1734            // Setup battle with moves
1735            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1736            assert!(layer.prepare(&tx).await);
1737            layer.apply(&tx).await;
1738
1739            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1740            assert!(layer.prepare(&tx).await);
1741            layer.apply(&tx).await;
1742
1743            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1744            assert!(layer.prepare(&tx).await);
1745            layer.apply(&tx).await;
1746
1747            // Commit and create new layer with advanced view to expire the lobby
1748            let changes = layer.commit();
1749            state.apply(changes).await;
1750            let new_seed = create_seed(&network_secret, 102);
1751            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1752
1753            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1754            assert!(layer.prepare(&tx).await);
1755            let events = layer.apply(&tx).await;
1756            assert_eq!(events.len(), 1);
1757
1758            // Get the battle expiry
1759            let battle_expiry = layer
1760                .view()
1761                .checked_add(MOVE_EXPIRY)
1762                .expect("view overflow");
1763
1764            // Send moves
1765            let move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
1766            let move_b = create_test_move_ciphertext(master_public, battle_expiry, 2);
1767
1768            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
1769            assert!(layer.prepare(&tx).await);
1770            layer.apply(&tx).await;
1771
1772            let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
1773            assert!(layer.prepare(&tx).await);
1774            layer.apply(&tx).await;
1775
1776            // Commit and create new layer with advanced view past expiry
1777            let changes = layer.commit();
1778            state.apply(changes).await;
1779            let new_seed = create_seed(&network_secret, battle_expiry + 1);
1780            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1781
1782            let wrong_signature =
1783                ops::sign_message::<MinSig>(&network_secret, Some(b"test"), b"wrong");
1784
1785            let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(wrong_signature));
1786            assert!(layer.prepare(&tx).await);
1787            let events = layer.apply(&tx).await;
1788            assert!(events.is_empty()); // Should produce no events
1789
1790            // Battle should still be active with pending moves
1791            let battle_key = layer.get(&Key::Account(actor_a.clone())).await.unwrap();
1792            if let Value::Account(account) = battle_key {
1793                assert!(account.battle.is_some());
1794            }
1795
1796            // Consume the layer at the end of the test
1797            let _ = layer.commit();
1798        });
1799    }
1800
1801    #[test]
1802    fn test_decryption_failure_defaults_to_no_move() {
1803        let executor = Runner::default();
1804        executor.start(|_| async move {
1805            let mut state = MockState::new();
1806            let (network_secret, master_public) = create_network_keypair();
1807            let seed = create_seed(&network_secret, 1);
1808            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1809
1810            let (signer_a, _) = create_test_actor(1);
1811            let (signer_b, _) = create_test_actor(2);
1812
1813            // Setup battle
1814            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1815            assert!(layer.prepare(&tx).await);
1816            layer.apply(&tx).await;
1817
1818            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1819            assert!(layer.prepare(&tx).await);
1820            layer.apply(&tx).await;
1821
1822            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1823            assert!(layer.prepare(&tx).await);
1824            layer.apply(&tx).await;
1825
1826            // Commit and create new layer with advanced view to expire the lobby
1827            let changes = layer.commit();
1828            state.apply(changes).await;
1829            let new_seed = create_seed(&network_secret, 102);
1830            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1831
1832            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1833            assert!(layer.prepare(&tx).await);
1834            let events = layer.apply(&tx).await;
1835            assert_eq!(events.len(), 1);
1836
1837            // Extract battle info from the event to know actual player assignments
1838            let (_, actual_player_a, _) = match &events[0] {
1839                Event::Matched {
1840                    battle,
1841                    expiry: _,
1842                    player_a,
1843                    player_a_creature: _,
1844                    player_a_stats: _,
1845                    player_b,
1846                    player_b_creature: _,
1847                    player_b_stats: _,
1848                } => (*battle, player_a.clone(), player_b.clone()),
1849                _ => panic!("Expected Matched event"),
1850            };
1851
1852            // Get the battle expiry
1853            let battle_expiry = layer
1854                .view()
1855                .checked_add(MOVE_EXPIRY)
1856                .expect("view overflow");
1857
1858            // Create moves based on actual player assignments
1859            // We want the player in position A to have a bad move that fails decryption
1860            let (bad_move_signer, bad_move_nonce, good_move_signer, good_move_nonce) =
1861                if actual_player_a == signer_a.public_key() {
1862                    // signer_a is in position A, signer_b is in position B
1863                    (&signer_a, 2, &signer_b, 2)
1864                } else {
1865                    // signer_b is in position A, signer_a is in position B
1866                    (&signer_b, 2, &signer_a, 2)
1867                };
1868
1869            // Create a ciphertext that will fail decryption (wrong target)
1870            let bad_move = create_test_move_ciphertext(master_public, 9999, 3); // Wrong expiry
1871            let good_move = create_test_move_ciphertext(master_public, battle_expiry, 2);
1872
1873            let tx =
1874                Transaction::sign(bad_move_signer, bad_move_nonce, Instruction::Move(bad_move));
1875            assert!(layer.prepare(&tx).await);
1876            layer.apply(&tx).await;
1877
1878            let tx = Transaction::sign(
1879                good_move_signer,
1880                good_move_nonce,
1881                Instruction::Move(good_move),
1882            );
1883            assert!(layer.prepare(&tx).await);
1884            layer.apply(&tx).await;
1885
1886            // Commit and create new layer with advanced view past expiry
1887            let changes = layer.commit();
1888            state.apply(changes).await;
1889            let new_seed = create_seed(&network_secret, battle_expiry + 1);
1890            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1891
1892            let signature = create_seed(&network_secret, battle_expiry);
1893
1894            let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
1895            assert!(layer.prepare(&tx).await);
1896            let events = layer.apply(&tx).await;
1897
1898            // Find the move event
1899            let move_event = events
1900                .iter()
1901                .find(|e| matches!(e, Event::Moved { .. }))
1902                .unwrap();
1903
1904            if let Event::Moved {
1905                player_a_move,
1906                player_b_move,
1907                ..
1908            } = move_event
1909            {
1910                // Player A's move should default to 0 (decryption failed)
1911                assert_eq!(*player_a_move, 0);
1912                // Player B's move should be 2 (successful decryption)
1913                assert_eq!(*player_b_move, 2);
1914            }
1915
1916            // Consume the layer at the end of the test
1917            let _ = layer.commit();
1918        });
1919    }
1920
1921    #[test]
1922    fn test_move_usage_limits_respected() {
1923        let executor = Runner::default();
1924        executor.start(|_| async move {
1925            let mut state = MockState::new();
1926            let (network_secret, master_public) = create_network_keypair();
1927            let seed = create_seed(&network_secret, 1);
1928            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1929            let (signer_a, _) = create_test_actor(1);
1930            let (signer_b, _) = create_test_actor(2);
1931
1932            // Setup battle
1933            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1934            assert!(layer.prepare(&tx).await);
1935            layer.apply(&tx).await;
1936            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1937            assert!(layer.prepare(&tx).await);
1938            layer.apply(&tx).await;
1939            let changes = layer.commit();
1940            state.apply(changes).await;
1941
1942            // Match
1943            let seed = create_seed(&network_secret, 1);
1944            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1945            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1946            assert!(layer.prepare(&tx).await);
1947            layer.apply(&tx).await;
1948            let changes = layer.commit();
1949            state.apply(changes).await;
1950
1951            // Commit and create new layer with advanced view to expire the lobby
1952            let new_seed = create_seed(&network_secret, 102);
1953            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1954            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1955            assert!(layer.prepare(&tx).await);
1956            let events = layer.apply(&tx).await;
1957            let Some(Event::Matched { player_a, .. }) = events.first() else {
1958                panic!("Expected Matched event");
1959            };
1960            let mut nonce_a = 2;
1961            let mut nonce_b = 2;
1962
1963            // Determine which signer is player_a in the battle
1964            let is_signer_a_player_a = signer_a.public_key() == *player_a;
1965
1966            // We'll have player_a use their strongest move repeatedly
1967            // Get player_a's account and find their strongest move
1968            let Some(Value::Account(account_player_a)) =
1969                layer.get(&Key::Account(player_a.clone())).await
1970            else {
1971                panic!("Player A account should exist");
1972            };
1973            let creature_player_a = account_player_a.creature.as_ref().unwrap();
1974            let move_limits = creature_player_a.get_move_usage_limits();
1975
1976            // Find the strongest move for player_a (non-zero, lowest limit)
1977            let mut strongest_move = 0;
1978            let mut min_limit = u8::MAX;
1979            for (i, &limit) in move_limits.iter().enumerate().skip(1) {
1980                if limit < min_limit {
1981                    min_limit = limit;
1982                    strongest_move = i as u8;
1983                }
1984            }
1985
1986            // Play rounds using the strongest move until we exceed its limit
1987            let battle_key = account_player_a.battle.unwrap();
1988
1989            // Play rounds up to the limit
1990            for _ in 0..min_limit {
1991                let battle_expiry = layer
1992                    .view()
1993                    .checked_add(MOVE_EXPIRY)
1994                    .expect("view overflow");
1995
1996                // player_a always uses their strongest move, player_b uses defense
1997                let (move_signer_a, move_signer_b) = if is_signer_a_player_a {
1998                    // signer_a is player_a, uses strongest move
1999                    // signer_b is player_b, uses defense (1)
2000                    (
2001                        create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2002                        create_test_move_ciphertext(master_public, battle_expiry, 1),
2003                    )
2004                } else {
2005                    // signer_a is player_b, uses defense (1)
2006                    // signer_b is player_a, uses strongest move
2007                    (
2008                        create_test_move_ciphertext(master_public, battle_expiry, 1),
2009                        create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2010                    )
2011                };
2012
2013                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_signer_a));
2014                assert!(layer.prepare(&tx).await);
2015                let events = layer.apply(&tx).await;
2016                assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2017                nonce_a += 1;
2018                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_signer_b));
2019                assert!(layer.prepare(&tx).await);
2020                let events = layer.apply(&tx).await;
2021                assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2022                nonce_b += 1;
2023
2024                // Settle the round
2025                let changes = layer.commit();
2026                state.apply(changes).await;
2027                let new_seed = create_seed(&network_secret, battle_expiry + 1);
2028                layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2029                let signature = create_seed(&network_secret, battle_expiry);
2030                let tx =
2031                    Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
2032                assert!(layer.prepare(&tx).await);
2033                let events = layer.apply(&tx).await;
2034                nonce_a += 1;
2035
2036                // Verify move was applied
2037                let move_event = events
2038                    .iter()
2039                    .find(|e| matches!(e, Event::Moved { .. }))
2040                    .unwrap();
2041                if let Event::Moved {
2042                    player_a_move,
2043                    player_b_move,
2044                    ..
2045                } = move_event
2046                {
2047                    // player_a always uses strongest_move, player_b always uses 1
2048                    assert_eq!(*player_a_move, strongest_move);
2049                    assert_eq!(*player_b_move, 1);
2050                }
2051
2052                // Check if battle is over
2053                if layer.get(&Key::Battle(battle_key)).await.is_none() {
2054                    break;
2055                }
2056            }
2057
2058            // If battle is still ongoing, try to use the strongest move one more time
2059            if let Some(Value::Battle { .. }) = layer.get(&Key::Battle(battle_key)).await {
2060                let battle_expiry = layer
2061                    .view()
2062                    .checked_add(MOVE_EXPIRY)
2063                    .expect("view overflow");
2064
2065                // Try to use the strongest move again (should exceed limit)
2066                let (move_signer_a, move_signer_b) = if is_signer_a_player_a {
2067                    // signer_a is player_a, tries to use strongest move again
2068                    // signer_b is player_b, uses defense (1)
2069                    (
2070                        create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2071                        create_test_move_ciphertext(master_public, battle_expiry, 1),
2072                    )
2073                } else {
2074                    // signer_a is player_b, uses defense (1)
2075                    // signer_b is player_a, tries to use strongest move again
2076                    (
2077                        create_test_move_ciphertext(master_public, battle_expiry, 1),
2078                        create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2079                    )
2080                };
2081
2082                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_signer_a));
2083                assert!(layer.prepare(&tx).await);
2084                layer.apply(&tx).await;
2085                nonce_a += 1;
2086                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_signer_b));
2087                assert!(layer.prepare(&tx).await);
2088                layer.apply(&tx).await;
2089
2090                // Settle the round
2091                let changes = layer.commit();
2092                state.apply(changes).await;
2093                let new_seed = create_seed(&network_secret, battle_expiry + 1);
2094                layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2095                let signature = create_seed(&network_secret, battle_expiry);
2096                let tx =
2097                    Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
2098                assert!(layer.prepare(&tx).await);
2099                let events = layer.apply(&tx).await;
2100
2101                // Verify move was defaulted to 0 (no move) due to limit exceeded
2102                let move_event = events
2103                    .iter()
2104                    .find(|e| matches!(e, Event::Moved { .. }))
2105                    .unwrap();
2106                if let Event::Moved {
2107                    player_a_move,
2108                    player_b_move,
2109                    ..
2110                } = move_event
2111                {
2112                    // player_a's move should be 0 (exceeded limit), player_b should still use 1
2113                    assert_eq!(*player_a_move, 0);
2114                    assert_eq!(*player_b_move, 1);
2115                }
2116            }
2117
2118            let _ = layer.commit();
2119        });
2120    }
2121
2122    #[test]
2123    fn test_move_usage_counts_persist_across_rounds() {
2124        let executor = Runner::default();
2125        executor.start(|_| async move {
2126            let mut state = MockState::new();
2127            let (network_secret, master_public) = create_network_keypair();
2128            let seed = create_seed(&network_secret, 1);
2129            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2130            let (signer_a, actor_a) = create_test_actor(1);
2131            let (signer_b, _) = create_test_actor(2);
2132
2133            // Setup battle
2134            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2135            assert!(layer.prepare(&tx).await);
2136            layer.apply(&tx).await;
2137            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2138            assert!(layer.prepare(&tx).await);
2139            layer.apply(&tx).await;
2140            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2141            assert!(layer.prepare(&tx).await);
2142            layer.apply(&tx).await;
2143
2144            let changes = layer.commit();
2145            state.apply(changes).await;
2146            let new_seed = create_seed(&network_secret, 102);
2147            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2148            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2149            assert!(layer.prepare(&tx).await);
2150            let events = layer.apply(&tx).await;
2151            let Some(Event::Matched { player_a, .. }) = events.first() else {
2152                panic!("Expected Matched event");
2153            };
2154
2155            let Some(Value::Account(account)) = layer.get(&Key::Account(actor_a.clone())).await
2156            else {
2157                panic!("Account should exist");
2158            };
2159            let battle_key = account.battle.unwrap();
2160
2161            // Play first round with move 2
2162            let battle_expiry = layer
2163                .view()
2164                .checked_add(MOVE_EXPIRY)
2165                .expect("view overflow");
2166            let move_a = create_test_move_ciphertext(master_public, battle_expiry, 3);
2167            let move_b = create_test_move_ciphertext(master_public, battle_expiry, 1);
2168
2169            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
2170            assert!(layer.prepare(&tx).await);
2171            let events = layer.apply(&tx).await;
2172            assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2173            let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
2174            assert!(layer.prepare(&tx).await);
2175            let events = layer.apply(&tx).await;
2176            assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2177
2178            let changes = layer.commit();
2179            state.apply(changes).await;
2180            let new_seed = create_seed(&network_secret, battle_expiry + 1);
2181            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2182            let signature = create_seed(&network_secret, battle_expiry);
2183            let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
2184            assert!(layer.prepare(&tx).await);
2185            let events = layer.apply(&tx).await;
2186
2187            // Check if battle ended
2188            let battle_ended = events.iter().any(|e| matches!(e, Event::Settled { .. }));
2189            if battle_ended {
2190                // Battle ended after first round, test is complete
2191                let _ = layer.commit();
2192                return;
2193            }
2194
2195            // Check move counts are updated
2196            let Some(Value::Battle {
2197                player_a_move_counts,
2198                player_b_move_counts,
2199                ..
2200            }) = layer.get(&Key::Battle(battle_key)).await
2201            else {
2202                panic!("Battle should exist");
2203            };
2204            if signer_a.public_key() == *player_a {
2205                assert_eq!(
2206                    player_a_move_counts[3], 1,
2207                    "Move 2 should have been used once"
2208                );
2209            } else {
2210                assert_eq!(
2211                    player_b_move_counts[3], 1,
2212                    "Move 2 should have been used once"
2213                );
2214            }
2215
2216            // Play second round with move 3
2217            let battle_expiry = layer
2218                .view()
2219                .checked_add(MOVE_EXPIRY)
2220                .expect("view overflow");
2221            let move_a = create_test_move_ciphertext(master_public, battle_expiry, 4);
2222            let move_b = create_test_move_ciphertext(master_public, battle_expiry, 1);
2223
2224            let tx = Transaction::sign(&signer_a, 4, Instruction::Move(move_a));
2225            assert!(layer.prepare(&tx).await);
2226            layer.apply(&tx).await;
2227            let tx = Transaction::sign(&signer_b, 3, Instruction::Move(move_b));
2228            assert!(layer.prepare(&tx).await);
2229            layer.apply(&tx).await;
2230
2231            let changes = layer.commit();
2232            state.apply(changes).await;
2233            let new_seed = create_seed(&network_secret, battle_expiry + 1);
2234            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2235            let signature = create_seed(&network_secret, battle_expiry);
2236            let tx = Transaction::sign(&signer_a, 5, Instruction::Settle(signature.signature));
2237            assert!(layer.prepare(&tx).await);
2238            layer.apply(&tx).await;
2239
2240            // Check move counts persist
2241            if let Some(Value::Battle {
2242                player_a_move_counts,
2243                player_b_move_counts,
2244                ..
2245            }) = layer.get(&Key::Battle(battle_key)).await
2246            {
2247                if signer_a.public_key() == *player_a {
2248                    assert_eq!(player_a_move_counts[3], 1, "Move 2 count should persist");
2249                    assert_eq!(
2250                        player_a_move_counts[4], 1,
2251                        "Move 3 should have been used once"
2252                    );
2253                } else {
2254                    assert_eq!(player_b_move_counts[3], 1, "Move 2 count should persist");
2255                    assert_eq!(
2256                        player_b_move_counts[4], 1,
2257                        "Move 3 should have been used once"
2258                    );
2259                }
2260            }
2261
2262            let _ = layer.commit();
2263        });
2264    }
2265
2266    #[test]
2267    fn test_creature_strength_method() {
2268        let executor = Runner::default();
2269        executor.start(|_| async move {
2270            let (network_secret, _) = create_network_keypair();
2271            let (_, actor) = create_test_actor(1);
2272            let seed = create_seed(&network_secret, 1);
2273            let creature = Creature::new(actor, 0, seed.signature);
2274
2275            // Test that health() returns the first trait
2276            assert_eq!(creature.health(), creature.traits[0]);
2277        });
2278    }
2279
2280    #[test]
2281    fn test_creature_get_move_strengths() {
2282        let executor = Runner::default();
2283        executor.start(|_| async move {
2284            let (network_secret, _) = create_network_keypair();
2285            let (_, actor) = create_test_actor(1);
2286            let seed = create_seed(&network_secret, 1);
2287            let creature = Creature::new(actor, 0, seed.signature);
2288
2289            let move_strengths = creature.get_move_strengths();
2290
2291            // Verify correct mapping
2292            assert_eq!(move_strengths[0], 0); // No-op
2293            assert_eq!(move_strengths[1], creature.traits[1]); // Defense
2294            assert_eq!(move_strengths[2], creature.traits[2]); // Attack 1
2295            assert_eq!(move_strengths[3], creature.traits[3]); // Attack 2
2296            assert_eq!(move_strengths[4], creature.traits[4]); // Attack 3
2297        });
2298    }
2299
2300    #[test]
2301    fn test_creature_actions() {
2302        let executor = Runner::default();
2303        executor.start(|_| async move {
2304            let (network_secret, _) = create_network_keypair();
2305            let (_, actor) = create_test_actor(1);
2306            let seed = create_seed(&network_secret, 1);
2307            let creature = Creature::new(actor, 0, seed.signature);
2308
2309            assert_eq!(creature.action(0, seed.signature), (false, 0));
2310            assert_eq!(
2311                creature.action(TOTAL_MOVES as u8, seed.signature),
2312                (false, 0)
2313            );
2314            assert_eq!(creature.action(u8::MAX, seed.signature), (false, 0));
2315        });
2316    }
2317
2318    #[test]
2319    fn test_creature_action_minimum_effectiveness() {
2320        let executor = Runner::default();
2321        executor.start(|_| async move {
2322            let (network_secret, _) = create_network_keypair();
2323            let (_, actor) = create_test_actor(1);
2324            let seed = create_seed(&network_secret, 1);
2325            let creature = Creature::new(actor, 0, seed.signature);
2326
2327            // Test multiple seeds to ensure minimum is 1/2 of max
2328            for i in 0..100 {
2329                // Create different signatures for testing
2330                let (sk, _) = create_network_keypair();
2331                let test_seed = ops::sign_message::<MinSig>(&sk, Some(b"test"), &[i; 32]);
2332
2333                // Check all moves
2334                for move_idx in 1..TOTAL_MOVES as u8 {
2335                    let (is_defense, power) = creature.action(move_idx, test_seed);
2336                    let max_power = creature.traits[move_idx as usize];
2337                    let min_expected = max_power / 2;
2338
2339                    // Verify the power is at least half of max
2340                    assert!(power >= min_expected);
2341
2342                    // Verify the power doesn't exceed max
2343                    assert!(power <= max_power);
2344
2345                    // Verify defense flag is correct
2346                    assert_eq!(is_defense, move_idx == 1);
2347                }
2348            }
2349        });
2350    }
2351
2352    #[test]
2353    fn test_generate_multiple_times() {
2354        let executor = Runner::default();
2355        executor.start(|_| async move {
2356            let state = MockState::new();
2357            let (network_secret, master_public) = create_network_keypair();
2358            let seed = create_seed(&network_secret, 1);
2359            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2360            let (signer, _) = create_test_actor(1);
2361
2362            // Generate first creature
2363            let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2364            assert!(layer.prepare(&tx).await);
2365            let events = layer.apply(&tx).await;
2366            assert_eq!(events.len(), 1);
2367            assert!(matches!(events[0], Event::Generated { .. }));
2368
2369            // Try to generate again (should replace existing)
2370            let tx = Transaction::sign(&signer, 1, Instruction::Generate);
2371            assert!(layer.prepare(&tx).await);
2372            let events = layer.apply(&tx).await;
2373            assert_eq!(events.len(), 1);
2374            assert!(matches!(events[0], Event::Generated { .. }));
2375
2376            let _ = layer.commit();
2377        });
2378    }
2379
2380    #[test]
2381    fn test_match_with_empty_lobby() {
2382        let executor = Runner::default();
2383        executor.start(|_| async move {
2384            let state = MockState::new();
2385            let (network_secret, master_public) = create_network_keypair();
2386            let seed = create_seed(&network_secret, 1);
2387            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2388            let (signer, actor) = create_test_actor(1);
2389
2390            // Generate creature first
2391            let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2392            assert!(layer.prepare(&tx).await);
2393            layer.apply(&tx).await;
2394
2395            // Try to match with empty lobby
2396            let tx = Transaction::sign(&signer, 1, Instruction::Match);
2397            assert!(layer.prepare(&tx).await);
2398            let events = layer.apply(&tx).await;
2399
2400            // Should be added to lobby
2401            assert_eq!(events.len(), 0); // No match event, just added to lobby
2402
2403            // Verify actor is in lobby
2404            if let Some(Value::Lobby { players, .. }) = layer.get(&Key::Lobby).await {
2405                assert!(players.contains(&actor));
2406            } else {
2407                panic!("Lobby should exist");
2408            }
2409
2410            let _ = layer.commit();
2411        });
2412    }
2413
2414    #[test]
2415    fn test_move_with_no_battle() {
2416        let executor = Runner::default();
2417        executor.start(|_| async move {
2418            let state = MockState::new();
2419            let (network_secret, master_public) = create_network_keypair();
2420            let seed = create_seed(&network_secret, 1);
2421            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2422            let (signer, _) = create_test_actor(1);
2423
2424            // Generate creature
2425            let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2426            assert!(layer.prepare(&tx).await);
2427            layer.apply(&tx).await;
2428
2429            // Try to move without being in battle
2430            let encrypted_move = create_test_move_ciphertext(master_public, 100, 1);
2431            let tx = Transaction::sign(&signer, 1, Instruction::Move(encrypted_move));
2432            assert!(layer.prepare(&tx).await);
2433            let events = layer.apply(&tx).await;
2434
2435            // Should return empty events (no-op)
2436            assert_eq!(events.len(), 0);
2437
2438            let _ = layer.commit();
2439        });
2440    }
2441
2442    #[test]
2443    fn test_settle_with_no_battle() {
2444        let executor = Runner::default();
2445        executor.start(|_| async move {
2446            let state = MockState::new();
2447            let (network_secret, master_public) = create_network_keypair();
2448            let seed = create_seed(&network_secret, 1);
2449            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2450            let (signer, _) = create_test_actor(1);
2451
2452            // Generate creature
2453            let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2454            assert!(layer.prepare(&tx).await);
2455            layer.apply(&tx).await;
2456
2457            // Try to settle without being in battle
2458            let signature = create_seed(&network_secret, 100);
2459            let tx = Transaction::sign(&signer, 1, Instruction::Settle(signature.signature));
2460            assert!(layer.prepare(&tx).await);
2461            let events = layer.apply(&tx).await;
2462
2463            // Should return empty events (no-op)
2464            assert_eq!(events.len(), 0);
2465
2466            let _ = layer.commit();
2467        });
2468    }
2469
2470    #[test]
2471    fn test_move_when_turn_expired() {
2472        let executor = Runner::default();
2473        executor.start(|_| async move {
2474            let mut state = MockState::new();
2475            let (network_secret, master_public) = create_network_keypair();
2476            let seed = create_seed(&network_secret, 1);
2477            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2478            let (signer_a, _) = create_test_actor(1);
2479            let (signer_b, _) = create_test_actor(2);
2480
2481            // Setup battle
2482            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2483            assert!(layer.prepare(&tx).await);
2484            layer.apply(&tx).await;
2485            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2486            assert!(layer.prepare(&tx).await);
2487            layer.apply(&tx).await;
2488            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2489            assert!(layer.prepare(&tx).await);
2490            layer.apply(&tx).await;
2491
2492            let changes = layer.commit();
2493            state.apply(changes).await;
2494            let new_seed = create_seed(&network_secret, 102);
2495            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2496            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2497            assert!(layer.prepare(&tx).await);
2498            layer.apply(&tx).await;
2499
2500            // Advance time past move expiry
2501            let changes = layer.commit();
2502            state.apply(changes).await;
2503            let new_seed = create_seed(&network_secret, 202);
2504            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed); // Past MOVE_EXPIRY
2505
2506            // Try to submit move after expiry
2507            let encrypted_move = create_test_move_ciphertext(master_public, 102 + MOVE_EXPIRY, 1);
2508            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(encrypted_move));
2509            assert!(layer.prepare(&tx).await);
2510            let events = layer.apply(&tx).await;
2511
2512            // Should return empty events (expired)
2513            assert_eq!(events.len(), 0);
2514
2515            let _ = layer.commit();
2516        });
2517    }
2518
2519    #[test]
2520    fn test_settle_before_turn_expired() {
2521        let executor = Runner::default();
2522        executor.start(|_| async move {
2523            let mut state = MockState::new();
2524            let (network_secret, master_public) = create_network_keypair();
2525            let seed = create_seed(&network_secret, 1);
2526            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2527            let (signer_a, _) = create_test_actor(1);
2528            let (signer_b, _) = create_test_actor(2);
2529
2530            // Setup battle
2531            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2532            assert!(layer.prepare(&tx).await);
2533            layer.apply(&tx).await;
2534            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2535            assert!(layer.prepare(&tx).await);
2536            layer.apply(&tx).await;
2537            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2538            assert!(layer.prepare(&tx).await);
2539            layer.apply(&tx).await;
2540
2541            let changes = layer.commit();
2542            state.apply(changes).await;
2543            let new_seed = create_seed(&network_secret, 102);
2544            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2545            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2546            assert!(layer.prepare(&tx).await);
2547            layer.apply(&tx).await;
2548
2549            let battle_expiry = layer
2550                .view()
2551                .checked_add(MOVE_EXPIRY)
2552                .expect("view overflow");
2553
2554            // Submit moves
2555            let move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
2556            let move_b = create_test_move_ciphertext(master_public, battle_expiry, 2);
2557            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
2558            assert!(layer.prepare(&tx).await);
2559            layer.apply(&tx).await;
2560            let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
2561            assert!(layer.prepare(&tx).await);
2562            layer.apply(&tx).await;
2563
2564            // Try to settle before expiry (should fail)
2565            let signature = create_seed(&network_secret, battle_expiry);
2566            let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
2567            assert!(layer.prepare(&tx).await);
2568            let events = layer.apply(&tx).await;
2569
2570            // Should return empty events (not expired yet)
2571            assert_eq!(events.len(), 0);
2572
2573            let _ = layer.commit();
2574        });
2575    }
2576
2577    #[test]
2578    fn test_double_move_submission() {
2579        let executor = Runner::default();
2580        executor.start(|_| async move {
2581            let mut state = MockState::new();
2582            let (network_secret, master_public) = create_network_keypair();
2583            let seed = create_seed(&network_secret, 1);
2584            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2585            let (signer_a, _) = create_test_actor(1);
2586            let (signer_b, _) = create_test_actor(2);
2587
2588            // Setup battle
2589            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2590            assert!(layer.prepare(&tx).await);
2591            layer.apply(&tx).await;
2592            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2593            assert!(layer.prepare(&tx).await);
2594            layer.apply(&tx).await;
2595            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2596            assert!(layer.prepare(&tx).await);
2597            layer.apply(&tx).await;
2598
2599            let changes = layer.commit();
2600            state.apply(changes).await;
2601            let new_seed = create_seed(&network_secret, 102);
2602            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2603            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2604            assert!(layer.prepare(&tx).await);
2605            layer.apply(&tx).await;
2606
2607            let battle_expiry = layer
2608                .view()
2609                .checked_add(MOVE_EXPIRY)
2610                .expect("view overflow");
2611
2612            // Submit first move
2613            let move_a1 = create_test_move_ciphertext(master_public, battle_expiry, 1);
2614            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a1));
2615            assert!(layer.prepare(&tx).await);
2616            layer.apply(&tx).await;
2617
2618            // Try to submit second move (should fail)
2619            let move_a2 = create_test_move_ciphertext(master_public, battle_expiry, 2);
2620            let tx = Transaction::sign(&signer_a, 3, Instruction::Move(move_a2));
2621            assert!(layer.prepare(&tx).await);
2622            let events = layer.apply(&tx).await;
2623
2624            // Should return empty events (already moved)
2625            assert_eq!(events.len(), 0);
2626
2627            let _ = layer.commit();
2628        });
2629    }
2630
2631    #[test]
2632    fn test_battle_with_all_health_scenarios() {
2633        let executor = Runner::default();
2634        executor.start(|_| async move {
2635            let mut state = MockState::new();
2636            let (network_secret, master_public) = create_network_keypair();
2637            let seed = create_seed(&network_secret, 1);
2638            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2639            let (signer_a, actor_a) = create_test_actor(1);
2640            let (signer_b, actor_b) = create_test_actor(2);
2641
2642            // Setup battle
2643            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2644            assert!(layer.prepare(&tx).await);
2645            layer.apply(&tx).await;
2646            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2647            assert!(layer.prepare(&tx).await);
2648            layer.apply(&tx).await;
2649            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2650            assert!(layer.prepare(&tx).await);
2651            layer.apply(&tx).await;
2652
2653            let changes = layer.commit();
2654            state.apply(changes).await;
2655            let new_seed = create_seed(&network_secret, 102);
2656            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2657            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2658            assert!(layer.prepare(&tx).await);
2659            layer.apply(&tx).await;
2660
2661            // Get initial healths to calculate number of rounds needed
2662            let Some(Value::Account(account_a)) = layer.get(&Key::Account(actor_a.clone())).await
2663            else {
2664                panic!("Account A should exist");
2665            };
2666            let Some(Value::Account(account_b)) = layer.get(&Key::Account(actor_b.clone())).await
2667            else {
2668                panic!("Account B should exist");
2669            };
2670
2671            let _max_health_a = account_a.creature.as_ref().unwrap().health();
2672            let _max_health_b = account_b.creature.as_ref().unwrap().health();
2673
2674            // Play rounds with both players attacking
2675            let mut nonce_a = 2;
2676            let mut nonce_b = 2;
2677            let mut round = 0;
2678
2679            loop {
2680                let battle_expiry = layer
2681                    .view()
2682                    .checked_add(MOVE_EXPIRY)
2683                    .expect("view overflow");
2684
2685                // Both players attack (not defend)
2686                let move_a = create_test_move_ciphertext(master_public, battle_expiry, 2);
2687                let move_b = create_test_move_ciphertext(master_public, battle_expiry, 3);
2688
2689                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_a));
2690                assert!(layer.prepare(&tx).await);
2691                layer.apply(&tx).await;
2692                nonce_a += 1;
2693                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_b));
2694                assert!(layer.prepare(&tx).await);
2695                layer.apply(&tx).await;
2696                nonce_b += 1;
2697
2698                // Settle
2699                let changes = layer.commit();
2700                state.apply(changes).await;
2701                let new_seed = create_seed(&network_secret, battle_expiry + 1);
2702                layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2703                let signature = create_seed(&network_secret, battle_expiry);
2704                let tx =
2705                    Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
2706                assert!(layer.prepare(&tx).await);
2707                let events = layer.apply(&tx).await;
2708                nonce_a += 1;
2709
2710                // Check if battle ended
2711                let settled = events.iter().any(|e| matches!(e, Event::Settled { .. }));
2712                if settled {
2713                    // Verify proper ELO update occurred
2714                    let Some(Value::Account(final_a)) =
2715                        layer.get(&Key::Account(actor_a.clone())).await
2716                    else {
2717                        panic!("Account A should exist");
2718                    };
2719                    let Some(Value::Account(final_b)) =
2720                        layer.get(&Key::Account(actor_b.clone())).await
2721                    else {
2722                        panic!("Account B should exist");
2723                    };
2724
2725                    // Check that ELO changed
2726                    assert_ne!(
2727                        final_a.stats.elo, account_a.stats.elo,
2728                        "ELO should have changed for player A"
2729                    );
2730                    assert_ne!(
2731                        final_b.stats.elo, account_b.stats.elo,
2732                        "ELO should have changed for player B"
2733                    );
2734
2735                    // Check win/loss/draw counters
2736                    let total_games_a =
2737                        final_a.stats.wins + final_a.stats.losses + final_a.stats.draws;
2738                    let total_games_b =
2739                        final_b.stats.wins + final_b.stats.losses + final_b.stats.draws;
2740                    assert_eq!(total_games_a, 1, "Player A should have exactly 1 game");
2741                    assert_eq!(total_games_b, 1, "Player B should have exactly 1 game");
2742
2743                    break;
2744                }
2745
2746                round += 1;
2747                if round > 100 {
2748                    panic!("Battle should have ended by now");
2749                }
2750            }
2751
2752            let _ = layer.commit();
2753        });
2754    }
2755
2756    #[test]
2757    fn test_player_a_defends_player_b_attacks() {
2758        let executor = Runner::default();
2759        executor.start(|_| async move {
2760            let mut state = MockState::new();
2761            let (network_secret, master_public) = create_network_keypair();
2762            let seed = create_seed(&network_secret, 1);
2763            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2764            let (signer_a, _) = create_test_actor(1);
2765            let (signer_b, _) = create_test_actor(2);
2766
2767            // Setup battle
2768            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2769            assert!(layer.prepare(&tx).await);
2770            layer.apply(&tx).await;
2771            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2772            assert!(layer.prepare(&tx).await);
2773            layer.apply(&tx).await;
2774            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2775            assert!(layer.prepare(&tx).await);
2776            layer.apply(&tx).await;
2777
2778            let changes = layer.commit();
2779            state.apply(changes).await;
2780            let new_seed = create_seed(&network_secret, 102);
2781            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2782            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2783            assert!(layer.prepare(&tx).await);
2784            let events = layer.apply(&tx).await;
2785            let Some(Event::Matched { player_a, .. }) = events.first() else {
2786                panic!("Expected Matched event");
2787            };
2788
2789            let battle_expiry = layer
2790                .view()
2791                .checked_add(MOVE_EXPIRY)
2792                .expect("view overflow");
2793
2794            // Player A defends (move 0), Player B attacks (move 2)
2795            let move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
2796            let move_b = create_test_move_ciphertext(master_public, battle_expiry, 3);
2797
2798            let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
2799            assert!(layer.prepare(&tx).await);
2800            layer.apply(&tx).await;
2801            let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
2802            assert!(layer.prepare(&tx).await);
2803            layer.apply(&tx).await;
2804
2805            let changes = layer.commit();
2806            state.apply(changes).await;
2807            let new_seed = create_seed(&network_secret, battle_expiry + 1);
2808            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2809            let signature = create_seed(&network_secret, battle_expiry);
2810            let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
2811            assert!(layer.prepare(&tx).await);
2812            let events = layer.apply(&tx).await;
2813
2814            // Find the move event to verify impacts
2815            let move_event = events
2816                .iter()
2817                .find(|e| matches!(e, Event::Moved { .. }))
2818                .unwrap();
2819            if let Event::Moved {
2820                player_a_power,
2821                player_b_power,
2822                ..
2823            } = move_event
2824            {
2825                // Player A defended (positive impact), Player B attacked (negative impact)
2826                if signer_a.public_key() == *player_a {
2827                    assert!(*player_a_power > 0, "Defense should have positive impact");
2828                    assert!(*player_b_power > 0, "Attack should have negative impact");
2829                } else {
2830                    assert!(*player_b_power > 0, "Attack should have positive impact");
2831                    assert!(*player_a_power > 0, "Defense should have negative impact");
2832                }
2833            }
2834
2835            let _ = layer.commit();
2836        });
2837    }
2838
2839    #[test]
2840    fn test_battle_times_out_after_max_rounds() {
2841        let executor = Runner::default();
2842        executor.start(|_| async move {
2843            let mut state = MockState::new();
2844            let (network_secret, master_public) = create_network_keypair();
2845            let seed = create_seed(&network_secret, 1);
2846            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2847            let (signer_a, actor_a) = create_test_actor(1);
2848            let (signer_b, actor_b) = create_test_actor(2);
2849
2850            // Generate creatures
2851            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2852            assert!(layer.prepare(&tx).await);
2853            layer.apply(&tx).await;
2854            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2855            assert!(layer.prepare(&tx).await);
2856            layer.apply(&tx).await;
2857            let changes = layer.commit();
2858            state.apply(changes).await;
2859
2860            // Match players
2861            let seed = create_seed(&network_secret, 2);
2862            layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2863            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2864            assert!(layer.prepare(&tx).await);
2865            layer.apply(&tx).await;
2866            let changes = layer.commit();
2867            state.apply(changes).await;
2868
2869            // Jump ahead to view 3
2870            let view = 2 + LOBBY_EXPIRY + 1;
2871            let seed = create_seed(&network_secret, view);
2872            layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2873            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2874            assert!(layer.prepare(&tx).await);
2875            let events = layer.apply(&tx).await;
2876            let Some(Event::Matched { battle, .. }) =
2877                events.iter().find(|e| matches!(e, Event::Matched { .. }))
2878            else {
2879                panic!("Battle should be matched");
2880            };
2881            let changes = layer.commit();
2882            state.apply(changes).await;
2883
2884            // Simulate MAX_BATTLE_ROUNDS rounds where both players do nothing (move 0)
2885            let mut nonce_a = 2;
2886            let mut nonce_b = 2;
2887
2888            let mut view = view + 1;
2889            for round in 0..MAX_BATTLE_ROUNDS {
2890                // Get battle expiry
2891                let Some(Value::Battle { expiry, .. }) = state.get(&Key::Battle(*battle)).await
2892                else {
2893                    panic!("Battle should exist");
2894                };
2895
2896                // Both players make no move (move 0)
2897                let seed = create_seed(&network_secret, view);
2898                layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2899                let move_a = create_test_move_ciphertext(master_public, expiry, 0);
2900                let move_b = create_test_move_ciphertext(master_public, expiry, 0);
2901
2902                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_a));
2903                assert!(layer.prepare(&tx).await);
2904                let events = layer.apply(&tx).await;
2905                assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2906                nonce_a += 1;
2907
2908                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_b));
2909                assert!(layer.prepare(&tx).await);
2910                let events = layer.apply(&tx).await;
2911                assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2912                nonce_b += 1;
2913                let changes = layer.commit();
2914                state.apply(changes).await;
2915
2916                // Advance time and settle
2917                view = expiry + 1;
2918                let new_seed = create_seed(&network_secret, view);
2919                let new_layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2920                layer = new_layer;
2921
2922                let expiry_seed = create_seed(&network_secret, expiry);
2923                let tx = Transaction::sign(
2924                    &signer_a,
2925                    nonce_a,
2926                    Instruction::Settle(expiry_seed.signature),
2927                );
2928                assert!(layer.prepare(&tx).await);
2929                let events = layer.apply(&tx).await;
2930                nonce_a += 1;
2931
2932                // Check if battle ended
2933                if let Some(Event::Settled { round, outcome, .. }) =
2934                    events.iter().find(|e| matches!(e, Event::Settled { .. }))
2935                {
2936                    assert_eq!(*round, MAX_BATTLE_ROUNDS);
2937                    assert_eq!(*outcome, Outcome::Draw);
2938
2939                    // Verify both players' draw counts increased
2940                    let Some(Value::Account(account_a_final)) =
2941                        layer.get(&Key::Account(actor_a.clone())).await
2942                    else {
2943                        panic!("Account A should exist");
2944                    };
2945                    let Some(Value::Account(account_b_final)) =
2946                        layer.get(&Key::Account(actor_b.clone())).await
2947                    else {
2948                        panic!("Account B should exist");
2949                    };
2950
2951                    assert_eq!(account_a_final.stats.draws, 1);
2952                    assert_eq!(account_b_final.stats.draws, 1);
2953                    assert!(account_a_final.battle.is_none());
2954                    assert!(account_b_final.battle.is_none());
2955
2956                    return; // Test passed
2957                }
2958
2959                // If we haven't reached MAX_BATTLE_ROUNDS yet, continue
2960                if round < MAX_BATTLE_ROUNDS - 1 {
2961                    assert!(
2962                        events.iter().any(|e| matches!(e, Event::Moved { .. })),
2963                        "Round {round}: Expected Moved event but got {events:?}",
2964                    );
2965                }
2966
2967                // Update view and state for next iteration
2968                let changes = layer.commit();
2969                state.apply(changes).await;
2970                view += 1;
2971            }
2972
2973            panic!("Battle should have ended in a draw after MAX_BATTLE_ROUNDS");
2974        });
2975    }
2976
2977    #[test]
2978    fn test_out_of_range_moves_handled_as_no_move() {
2979        let executor = Runner::default();
2980        executor.start(|_| async move {
2981            let mut state = MockState::new();
2982            let (network_secret, master_public) = create_network_keypair();
2983            let seed = create_seed(&network_secret, 1);
2984            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2985            let (signer_a, actor_a) = create_test_actor(1);
2986            let (signer_b, _) = create_test_actor(2);
2987
2988            // Setup battle
2989            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2990            assert!(layer.prepare(&tx).await);
2991            layer.apply(&tx).await;
2992            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2993            assert!(layer.prepare(&tx).await);
2994            layer.apply(&tx).await;
2995            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2996            assert!(layer.prepare(&tx).await);
2997            layer.apply(&tx).await;
2998
2999            let changes = layer.commit();
3000            state.apply(changes).await;
3001            let new_seed = create_seed(&network_secret, 102);
3002            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3003            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
3004            assert!(layer.prepare(&tx).await);
3005            layer.apply(&tx).await;
3006            let changes = layer.commit();
3007            state.apply(changes).await;
3008
3009            // Get the battle key and determine player positions
3010            let Some(Value::Account(account_a)) = state.get(&Key::Account(actor_a.clone())).await
3011            else {
3012                panic!("Account A should exist");
3013            };
3014            let battle_key = account_a.battle.unwrap();
3015
3016            // Get battle to determine player positions
3017            let Some(Value::Battle { player_a, .. }) = state.get(&Key::Battle(battle_key)).await
3018            else {
3019                panic!("Battle should exist");
3020            };
3021
3022            // Submit out-of-range moves
3023            let Some(Value::Battle { expiry, .. }) = state.get(&Key::Battle(battle_key)).await
3024            else {
3025                panic!("Battle should exist");
3026            };
3027
3028            // Test various out-of-range values
3029            let out_of_range_moves = vec![
3030                5,   // Just past ALLOWED_MOVES (4)
3031                10,  // Way out of range
3032                100, // Very large
3033                255, // u8::MAX
3034            ];
3035
3036            // Test each out-of-range move
3037            let mut next_expiry = expiry;
3038            let mut nonce_a = 2;
3039            let mut nonce_b = 2;
3040            for out_of_range_move in out_of_range_moves {
3041                // Create new layer
3042                let new_seed = create_seed(&network_secret, next_expiry);
3043                layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3044
3045                // Player A (whichever signer is in that position) submits out-of-range move
3046                let move_out_of_range =
3047                    create_test_move_ciphertext(master_public, next_expiry, out_of_range_move);
3048                let tx =
3049                    Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_out_of_range));
3050                assert!(layer.prepare(&tx).await);
3051                let events = layer.apply(&tx).await;
3052                assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
3053                nonce_a += 1;
3054
3055                // Player B submits valid defense move
3056                let move_valid = create_test_move_ciphertext(master_public, next_expiry, 1);
3057                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_valid));
3058                assert!(layer.prepare(&tx).await);
3059                let events = layer.apply(&tx).await;
3060                assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
3061                nonce_b += 1;
3062                let changes = layer.commit();
3063                state.apply(changes).await;
3064
3065                // Settle the round
3066                let settle_seed = create_seed(&network_secret, next_expiry + 1);
3067                layer = Layer::new(&state, master_public, TEST_NAMESPACE, settle_seed);
3068                let signature = create_seed(&network_secret, next_expiry);
3069                let tx =
3070                    Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3071                assert!(layer.prepare(&tx).await);
3072                let events = layer.apply(&tx).await;
3073                nonce_a += 1;
3074
3075                // Check that the move was treated as no-op
3076                let Some(Event::Moved {
3077                    expiry,
3078                    player_a_move,
3079                    player_b_move,
3080                    ..
3081                }) = events.first()
3082                else {
3083                    panic!("Expected Moved event");
3084                };
3085                if signer_a.public_key() == player_a {
3086                    // Player A's out-of-range move should be treated as 0 (no move)
3087                    assert_eq!(*player_a_move, 0);
3088                    // Player B's valid move should remain unchanged
3089                    assert_eq!(*player_b_move, 1);
3090                } else {
3091                    assert_eq!(*player_a_move, 1);
3092                    assert_eq!(*player_b_move, 0);
3093                }
3094                next_expiry = *expiry;
3095
3096                // Check that the battle is still ongoing (no one should have won from a no-op)
3097                let Some(Value::Account(account)) = layer.get(&Key::Account(actor_a.clone())).await
3098                else {
3099                    panic!("Account should exist");
3100                };
3101                assert!(account.battle.is_some(), "Battle should still be ongoing");
3102
3103                // Update state
3104                let changes = layer.commit();
3105                state.apply(changes).await;
3106            }
3107        });
3108    }
3109
3110    #[test]
3111    fn test_prefer_result_over_draw_when_player_killed_in_last_round() {
3112        let executor = Runner::default();
3113        executor.start(|_| async move {
3114            // Setup battle
3115            let mut state = MockState::new();
3116            let (network_secret, master_public) = create_network_keypair();
3117            let seed = create_seed(&network_secret, 1);
3118            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3119            let (signer_a, actor_a) = create_test_actor(1);
3120            let (signer_b, actor_b) = create_test_actor(2);
3121
3122            // Generate creatures
3123            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
3124            assert!(layer.prepare(&tx).await);
3125            layer.apply(&tx).await;
3126            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
3127            assert!(layer.prepare(&tx).await);
3128            layer.apply(&tx).await;
3129
3130            // Match players
3131            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
3132            assert!(layer.prepare(&tx).await);
3133            layer.apply(&tx).await;
3134
3135            // Commit and advance view to expire lobby
3136            let changes = layer.commit();
3137            state.apply(changes).await;
3138            let new_seed = create_seed(&network_secret, 102);
3139            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3140            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
3141            assert!(layer.prepare(&tx).await);
3142            let events = layer.apply(&tx).await;
3143
3144            // Get battle info and determine which signer is which player
3145            let (battle, actual_player_a, _actual_player_b) = if let Event::Matched {
3146                battle,
3147                player_a,
3148                player_b,
3149                ..
3150            } = &events[0]
3151            {
3152                (*battle, player_a.clone(), player_b.clone())
3153            } else {
3154                panic!("Expected Matched event");
3155            };
3156
3157            // Determine which signer corresponds to which battle position
3158            let is_signer_a_player_a = actual_player_a == actor_a;
3159
3160            // Commit the state and directly modify the battle to set both players' health to 1
3161            let changes = layer.commit();
3162            state.apply(changes).await;
3163
3164            // Get the battle and modify health values
3165            let Some(Value::Battle {
3166                expiry,
3167                round,
3168                player_a,
3169                player_a_max_health,
3170                player_a_pending,
3171                player_a_move_counts,
3172                player_b,
3173                player_b_max_health,
3174                player_b_pending,
3175                player_b_move_counts,
3176                ..
3177            }) = state.get(&Key::Battle(battle)).await
3178            else {
3179                panic!("Battle should exist");
3180            };
3181            state
3182                .insert(
3183                    Key::Battle(battle),
3184                    Value::Battle {
3185                        expiry,
3186                        round,
3187                        player_a: player_a.clone(),
3188                        player_a_max_health,
3189                        player_a_health: 1,
3190                        player_a_pending,
3191                        player_a_move_counts,
3192                        player_b: player_b.clone(),
3193                        player_b_max_health,
3194                        player_b_health: 1,
3195                        player_b_pending,
3196                        player_b_move_counts,
3197                    },
3198                )
3199                .await;
3200
3201            // Simulate rounds until we're at the last round
3202            let mut nonce_a = 2;
3203            let mut nonce_b = 2;
3204            let mut view = 103;
3205
3206            // Skip to just before the last round
3207            for _ in 0..(MAX_BATTLE_ROUNDS - 1) {
3208                let seed = create_seed(&network_secret, view);
3209                layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3210
3211                let Some(Value::Battle { expiry, .. }) = layer.get(&Key::Battle(battle)).await
3212                else {
3213                    panic!("Battle should exist");
3214                };
3215                let battle_expiry = expiry;
3216
3217                // Both players do nothing
3218                let move_a = create_test_move_ciphertext(master_public, battle_expiry, 0);
3219                let move_b = create_test_move_ciphertext(master_public, battle_expiry, 0);
3220
3221                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_a));
3222                assert!(layer.prepare(&tx).await);
3223                layer.apply(&tx).await;
3224                nonce_a += 1;
3225
3226                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_b));
3227                assert!(layer.prepare(&tx).await);
3228                layer.apply(&tx).await;
3229                nonce_b += 1;
3230
3231                // Commit and advance to settle
3232                let changes = layer.commit();
3233                state.apply(changes).await;
3234                view = battle_expiry + 1;
3235                let seed = create_seed(&network_secret, view);
3236                layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3237
3238                // Settle the round
3239                let signature = create_seed(&network_secret, battle_expiry);
3240                let tx =
3241                    Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3242                assert!(layer.prepare(&tx).await);
3243                let events = layer.apply(&tx).await;
3244                nonce_a += 1;
3245
3246                // Verify battle continues
3247                assert!(events.iter().any(|e| matches!(e, Event::Moved { .. })));
3248                assert!(!events.iter().any(|e| matches!(e, Event::Settled { .. })));
3249
3250                let changes = layer.commit();
3251                state.apply(changes).await;
3252                view += 1;
3253            }
3254
3255            // Now we're at round MAX_BATTLE_ROUNDS (the last round)
3256            // Both players have health = 1
3257            let seed = create_seed(&network_secret, view);
3258            layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3259            let Some(Value::Battle { expiry, .. }) = layer.get(&Key::Battle(battle)).await else {
3260                panic!("Battle should exist");
3261            };
3262
3263            // We want battle's player_a to attack and player_b to do nothing
3264            // This should result in player_a winning (not a draw)
3265            if is_signer_a_player_a {
3266                // signer_a is player_a (the attacker)
3267                let attack_move = create_test_move_ciphertext(master_public, expiry, 2); // Any attack move
3268                let no_move = create_test_move_ciphertext(master_public, expiry, 0); // No action
3269
3270                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(attack_move));
3271                assert!(layer.prepare(&tx).await);
3272                layer.apply(&tx).await;
3273                nonce_a += 1;
3274
3275                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(no_move));
3276                assert!(layer.prepare(&tx).await);
3277                layer.apply(&tx).await;
3278            } else {
3279                // signer_b is player_a (the attacker)
3280                let no_move = create_test_move_ciphertext(master_public, expiry, 0); // No action
3281                let attack_move = create_test_move_ciphertext(master_public, expiry, 2); // Any attack move
3282
3283                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(no_move));
3284                assert!(layer.prepare(&tx).await);
3285                layer.apply(&tx).await;
3286                nonce_a += 1;
3287
3288                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(attack_move));
3289                assert!(layer.prepare(&tx).await);
3290                layer.apply(&tx).await;
3291            }
3292
3293            // Commit and advance to settle
3294            let changes = layer.commit();
3295            state.apply(changes).await;
3296            view = expiry + 1;
3297            let new_seed = create_seed(&network_secret, view);
3298            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3299
3300            // Settle the final round
3301            let signature = create_seed(&network_secret, expiry);
3302            let tx =
3303                Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3304            assert!(layer.prepare(&tx).await);
3305            let events = layer.apply(&tx).await;
3306
3307            // Verify the outcome
3308            let settled_event = events.iter().find(|e| matches!(e, Event::Settled { .. }));
3309            assert!(settled_event.is_some(), "Battle should have been settled");
3310            if let Some(Event::Settled { outcome, round, .. }) = settled_event {
3311                assert_eq!(
3312                    *round, MAX_BATTLE_ROUNDS,
3313                    "Battle should end at MAX_BATTLE_ROUNDS"
3314                );
3315                assert_eq!(
3316                    *outcome,
3317                    Outcome::PlayerA,
3318                    "Player A should win by killing Player B in the last round"
3319                );
3320            }
3321
3322            // Verify final account states
3323            let final_account_a =
3324                if let Some(Value::Account(acc)) = layer.get(&Key::Account(actor_a)).await {
3325                    acc
3326                } else {
3327                    panic!("Account A not found after battle");
3328                };
3329            let final_account_b =
3330                if let Some(Value::Account(acc)) = layer.get(&Key::Account(actor_b)).await {
3331                    acc
3332                } else {
3333                    panic!("Account B not found after battle");
3334                };
3335            if is_signer_a_player_a {
3336                // signer_a was player_a (winner)
3337                assert_eq!(
3338                    final_account_a.stats.wins, 1,
3339                    "Signer A (as Player A) should have won"
3340                );
3341                assert_eq!(final_account_a.stats.draws, 0);
3342                assert_eq!(
3343                    final_account_b.stats.losses, 1,
3344                    "Signer B (as Player B) should have lost"
3345                );
3346                assert_eq!(final_account_b.stats.draws, 0);
3347            } else {
3348                // signer_b was player_a (winner)
3349                assert_eq!(
3350                    final_account_b.stats.wins, 1,
3351                    "Signer B (as Player A) should have won"
3352                );
3353                assert_eq!(final_account_b.stats.draws, 0);
3354                assert_eq!(
3355                    final_account_a.stats.losses, 1,
3356                    "Signer A (as Player B) should have lost"
3357                );
3358                assert_eq!(final_account_a.stats.draws, 0);
3359            }
3360
3361            let _ = layer.commit();
3362        });
3363    }
3364
3365    #[test]
3366    fn test_defense_moves_never_exceed_max_health() {
3367        let executor = Runner::default();
3368        executor.start(|_| async move {
3369            // This test verifies that using defense moves never causes health to exceed the creature's maximum health
3370            let mut state = MockState::new();
3371            let (network_secret, master_public) = create_network_keypair();
3372            let seed = create_seed(&network_secret, 1);
3373            let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3374
3375            let (signer_a, _) = create_test_actor(1);
3376            let (signer_b, _) = create_test_actor(2);
3377
3378            // Generate creatures
3379            let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
3380            assert!(layer.prepare(&tx).await);
3381            layer.apply(&tx).await;
3382
3383            let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
3384            assert!(layer.prepare(&tx).await);
3385            layer.apply(&tx).await;
3386
3387            // Match players
3388            let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
3389            assert!(layer.prepare(&tx).await);
3390            layer.apply(&tx).await;
3391
3392            // Commit and advance view to expire lobby
3393            let changes = layer.commit();
3394            state.apply(changes).await;
3395            let new_seed = create_seed(&network_secret, 102);
3396            layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3397
3398            let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
3399            assert!(layer.prepare(&tx).await);
3400            let events = layer.apply(&tx).await;
3401            assert_eq!(events.len(), 1);
3402
3403            // Get battle info
3404            let Some(Event::Matched { battle, .. }) = events.first() else {
3405                panic!("Expected Matched event");
3406            };
3407
3408            // Play several rounds where both players use defense moves
3409            let mut nonce_a = 2;
3410            let mut nonce_b = 2;
3411            let mut view = 103;
3412            let changes = layer.commit();
3413            state.apply(changes).await;
3414
3415            for round in 0..5 {
3416                let seed = create_seed(&network_secret, view);
3417                layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3418
3419                let Some(Value::Battle { expiry, .. }) = layer.get(&Key::Battle(*battle)).await
3420                else {
3421                    panic!("Battle should exist");
3422                };
3423                let battle_expiry = expiry;
3424
3425                // Both players use defense moves (move 1)
3426                let defense_move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
3427                let defense_move_b = create_test_move_ciphertext(master_public, battle_expiry, 1);
3428
3429                let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(defense_move_a));
3430                assert!(layer.prepare(&tx).await);
3431                layer.apply(&tx).await;
3432                nonce_a += 1;
3433
3434                let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(defense_move_b));
3435                assert!(layer.prepare(&tx).await);
3436                layer.apply(&tx).await;
3437                nonce_b += 1;
3438
3439                // Commit and advance to settle
3440                let changes = layer.commit();
3441                state.apply(changes).await;
3442                view = battle_expiry + 1;
3443                let seed = create_seed(&network_secret, view);
3444                layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3445
3446                // Settle the round
3447                let signature = create_seed(&network_secret, battle_expiry);
3448                let tx =
3449                    Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3450                assert!(layer.prepare(&tx).await);
3451                let events = layer.apply(&tx).await;
3452                nonce_a += 1;
3453
3454                // Check health values after the round
3455                assert!(
3456                    events.iter().any(|e| matches!(e, Event::Moved { .. })),
3457                    "Should have a Moved event"
3458                );
3459
3460                // Check the battle state to verify health values
3461                if let Some(Value::Battle {
3462                    player_a_health,
3463                    player_a_max_health,
3464                    player_b_health,
3465                    player_b_max_health,
3466                    ..
3467                }) = layer.get(&Key::Battle(*battle)).await
3468                {
3469                    // Verify that health never exceeds maximum
3470                    assert!(player_a_health <= player_a_max_health);
3471                    assert!(player_b_health <= player_b_max_health);
3472                } else {
3473                    panic!("Battle should exist after round {round}");
3474                }
3475
3476                let changes = layer.commit();
3477                state.apply(changes).await;
3478                view += 1;
3479            }
3480        });
3481    }
3482}