Repository: BibliothecaDAO/eternum
Pull request #2779: Feat/troop raid
Author: login: credence0x
Created: 2025-03-19T22:45:13Z | Merged: 2025-03-20T08:58:26Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Expanded troop damage settings with detailed raid damage percentage parameters for improved filtering and configuration.
	- Introduced a new raid system that enables strategic interactions between units with outcome determination based on weighted probabilities.
	- Enhanced configuration displays, offering clearer visualization of raid damage data.

- **Refactor**
	- Streamlined combat mechanics by decoupling damage calculations from attack logic for improved clarity and maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/client/apps/landing/schema.graphql b/client/apps/landing/schema.graphql
index a5dbf20473e..5c7c76c9dda 100644
--- a/client/apps/landing/schema.graphql
+++ b/client/apps/landing/schema.graphql
@@ -8738,6 +8738,17 @@ input s1_eternum_WorldConfig_troop_damage_configWhereInput {
   damage_biome_bonus_numNEQ: u16
   damage_biome_bonus_numNOTIN: [u16]
   damage_biome_bonus_numNOTLIKE: u16
+  damage_raid_percent_num: u16
+  damage_raid_percent_numEQ: u16
+  damage_raid_percent_numGT: u16
+  damage_raid_percent_numGTE: u16
+  damage_raid_percent_numIN: [u16]
+  damage_raid_percent_numLIKE: u16
+  damage_raid_percent_numLT: u16
+  damage_raid_percent_numLTE: u16
+  damage_raid_percent_numNEQ: u16
+  damage_raid_percent_numNOTIN: [u16]
+  damage_raid_percent_numNOTLIKE: u16
   damage_c0: u128
   damage_c0EQ: u128
   damage_c0GT: u128
diff --git a/client/apps/landing/src/hooks/gql/graphql.ts b/client/apps/landing/src/hooks/gql/graphql.ts
index 8e8ea059b9e..63d858d231f 100644
--- a/client/apps/landing/src/hooks/gql/graphql.ts
+++ b/client/apps/landing/src/hooks/gql/graphql.ts
@@ -6924,6 +6924,17 @@ export type S1_Eternum_WorldConfig_Troop_Damage_ConfigWhereInput = {
   damage_biome_bonus_numNEQ?: InputMaybe<Scalars['u16']['input']>;
   damage_biome_bonus_numNOTIN?: InputMaybe<Array<InputMaybe<Scalars['u16']['input']>>>;
   damage_biome_bonus_numNOTLIKE?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_num?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numEQ?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numGT?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numGTE?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numIN?: InputMaybe<Array<InputMaybe<Scalars['u16']['input']>>>;
+  damage_raid_percent_numLIKE?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numLT?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numLTE?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numNEQ?: InputMaybe<Scalars['u16']['input']>;
+  damage_raid_percent_numNOTIN?: InputMaybe<Array<InputMaybe<Scalars['u16']['input']>>>;
+  damage_raid_percent_numNOTLIKE?: InputMaybe<Scalars['u16']['input']>;
   damage_c0?: InputMaybe<Scalars['u128']['input']>;
   damage_c0EQ?: InputMaybe<Scalars['u128']['input']>;
   damage_c0GT?: InputMaybe<Scalars['u128']['input']>;
diff --git a/config/deployer/config.ts b/config/deployer/config.ts
index cb184a5a108..b7947c4d43f 100644
--- a/config/deployer/config.ts
+++ b/config/deployer/config.ts
@@ -517,6 +517,7 @@ export const setTroopConfig = async (config: Config) => {
       t1DamageValue: t1_damage_value,
       t2DamageMultiplier: t2_damage_multiplier,
       t3DamageMultiplier: t3_damage_multiplier,
+      damageRaidPercentNum: damage_raid_percent_num,
       damageBiomeBonusNum: damage_biome_bonus_num,
       damageScalingFactor: damage_scaling_factor,
       damageBetaSmall: damage_beta_small,
@@ -557,6 +558,7 @@ export const setTroopConfig = async (config: Config) => {
       t1_damage_value: t1_damage_value,
       t2_damage_multiplier: t2_damage_multiplier,
       t3_damage_multiplier: t3_damage_multiplier,
+      damage_raid_percent_num: damage_raid_percent_num,
       damage_biome_bonus_num: damage_biome_bonus_num,
       damage_scaling_factor: damage_scaling_factor,
       damage_beta_small: damage_beta_small,
@@ -597,6 +599,7 @@ export const setTroopConfig = async (config: Config) => {
     │  ${chalk.gray("T1 Damage Value:")}      ${chalk.white(calldata.damage_config.t1_damage_value)}
     │  ${chalk.gray("T2 Damage Multiplier:")}     ${chalk.white(calldata.damage_config.t2_damage_multiplier)}
     │  ${chalk.gray("T3 Damage Multiplier:")}    ${chalk.white(calldata.damage_config.t3_damage_multiplier)}
+    │  ${chalk.gray("Damage Raid Percent:")}         ${chalk.white(calldata.damage_config.damage_raid_percent_num / 10_000)}%
     │  ${chalk.gray("Damage Biome Bonus:")}         ${chalk.white(calldata.damage_config.damage_biome_bonus_num)}
     │  ${chalk.gray("Damage Scaling Factor:")}         ${chalk.white(calldata.damage_config.damage_scaling_factor)}
     │  ${chalk.gray("Damage Beta Small:")}             ${chalk.white(calldata.damage_config.damage_beta_small)}
diff --git a/config/environments/_shared_.ts b/config/environments/_shared_.ts
index bce04785f56..ff65f94c222 100644
--- a/config/environments/_shared_.ts
+++ b/config/environments/_shared_.ts
@@ -32,6 +32,7 @@ import {
   TROOP_DAMAGE_BIOME_BONUS_NUM,
   TROOP_DAMAGE_C0,
   TROOP_DAMAGE_DELTA,
+  TROOP_DAMAGE_RAID_PERCENT_NUM,
   TROOP_DAMAGE_SCALING_FACTOR,
   TROOP_EXPLORE_FISH_COST,
   TROOP_EXPLORE_STAMINA_COST,
@@ -256,6 +257,7 @@ export const EternumGlobalConfig: Config = {
       t1DamageValue: TROOP_BASE_DAMAGE,
       t2DamageMultiplier: TROOP_T2_DAMAGE_MULTIPLIER,
       t3DamageMultiplier: TROOP_T3_DAMAGE_MULTIPLIER,
+      damageRaidPercentNum: TROOP_DAMAGE_RAID_PERCENT_NUM,
       damageBiomeBonusNum: TROOP_DAMAGE_BIOME_BONUS_NUM,
       damageScalingFactor: TROOP_DAMAGE_SCALING_FACTOR,
       damageBetaSmall: TROOP_DAMAGE_BETA_SMALL,
diff --git a/config/environments/utils/troop.ts b/config/environments/utils/troop.ts
index 974b543fcbd..0019f64aa1f 100644
--- a/config/environments/utils/troop.ts
+++ b/config/environments/utils/troop.ts
@@ -23,6 +23,9 @@ export const TROOPS_FOOD_CONSUMPTION: Record<number, TroopFoodConsumption> = {
   },
 };
 
+export const TROOP_DAMAGE_RAID_PERCENT_NUM = 1_000; //10%
+
+
 export const TROOP_BASE_DAMAGE = 1844674407370955161600n; // 100
 export const TROOP_T2_DAMAGE_MULTIPLIER = 46116860184273879040n; // 2.5
 export const TROOP_T3_DAMAGE_MULTIPLIER = 129127208515966861312n; // 7
diff --git a/contracts/game/dojo_dev.toml b/contracts/game/dojo_dev.toml
index 111b5da9c59..eae780b1871 100644
--- a/contracts/game/dojo_dev.toml
+++ b/contracts/game/dojo_dev.toml
@@ -32,6 +32,7 @@ website = "https://alpha-eternum.realms.world/"
     "s1_eternum-trade_systems",
     "s1_eternum-troop_management_systems",
 	"s1_eternum-troop_battle_systems",
+	"s1_eternum-troop_raid_systems",
     "s1_eternum-troop_movement_systems",
     "s1_eternum-troop_movement_util_systems",
 	"s1_eternum-season_systems"
diff --git a/contracts/game/dojo_local.toml b/contracts/game/dojo_local.toml
index 111b5da9c59..eae780b1871 100644
--- a/contracts/game/dojo_local.toml
+++ b/contracts/game/dojo_local.toml
@@ -32,6 +32,7 @@ website = "https://alpha-eternum.realms.world/"
     "s1_eternum-trade_systems",
     "s1_eternum-troop_management_systems",
 	"s1_eternum-troop_battle_systems",
+	"s1_eternum-troop_raid_systems",
     "s1_eternum-troop_movement_systems",
     "s1_eternum-troop_movement_util_systems",
 	"s1_eternum-season_systems"
diff --git a/contracts/game/dojo_sepolia.toml b/contracts/game/dojo_sepolia.toml
index eb58146e07a..efc7e89c1e5 100644
--- a/contracts/game/dojo_sepolia.toml
+++ b/contracts/game/dojo_sepolia.toml
@@ -32,6 +32,7 @@ website = "https://eternum.realms.world/"
     "s1_eternum-trade_systems",
     "s1_eternum-troop_management_systems",
 	"s1_eternum-troop_battle_systems",
+	"s1_eternum-troop_raid_systems",
     "s1_eternum-troop_movement_systems",
     "s1_eternum-troop_movement_util_systems",
 	"s1_eternum-season_systems"
diff --git a/contracts/game/dojo_slot.toml b/contracts/game/dojo_slot.toml
index d11e8a47ac2..46de5a1e611 100644
--- a/contracts/game/dojo_slot.toml
+++ b/contracts/game/dojo_slot.toml
@@ -33,6 +33,7 @@ website = "https://alpha-eternum.realms.world/"
     "s1_eternum-trade_systems",
     "s1_eternum-troop_management_systems",
 	"s1_eternum-troop_battle_systems",
+	"s1_eternum-troop_raid_systems",
     "s1_eternum-troop_movement_systems",
     "s1_eternum-troop_movement_util_systems",
 	"s1_eternum-season_systems"
diff --git a/contracts/game/src/models/config.cairo b/contracts/game/src/models/config.cairo
index efa15a5af2d..fc7b8e87121 100644
--- a/contracts/game/src/models/config.cairo
+++ b/contracts/game/src/models/config.cairo
@@ -348,6 +348,7 @@ pub struct TickConfig {
 // todo: regroup meaningfully to avoid retrieving too many fields
 #[derive(Copy, Drop, Serde, IntrospectPacked, Debug, PartialEq, Default)]
 pub struct TroopDamageConfig {
+    pub damage_raid_percent_num: u16,
     // Combat modifiers. Used for biome damage calculations
     pub damage_biome_bonus_num: u16,
     // Used in damage calculations for troop scaling
diff --git a/contracts/game/src/models/troop.cairo b/contracts/game/src/models/troop.cairo
index 023fdfd293c..1afbfdd0374 100644
--- a/contracts/game/src/models/troop.cairo
+++ b/contracts/game/src/models/troop.cairo
@@ -464,14 +464,14 @@ pub impl TroopsImpl of TroopsTrait {
         return BETA_EFF;
     }
 
-    fn attack(
+    fn damage(
         ref self: Troops,
         ref bravo: Troops,
         biome: Biome,
         troop_stamina_config: TroopStaminaConfig,
         troop_damage_config: TroopDamageConfig,
         current_tick: u64,
-    ) {
+    ) -> (u128, u128) {
         assert!(self.count.is_non_zero(), "you have no troops");
         assert!(bravo.count.is_non_zero(), "the defender has no troops");
         assert!(biome != Biome::None, "biome is not set");
@@ -537,9 +537,31 @@ pub impl TroopsImpl of TroopsTrait {
             / BRAVO_TIER_BONUS
             / TOTAL_NUM_TROOPS.pow(EFFECTIVE_BETA));
 
+        self = alpha;
+
+        (
+            ALPHA_DAMAGE_DEALT.round().try_into().unwrap() * RESOURCE_PRECISION,
+            BRAVO_DAMAGE_DEALT.round().try_into().unwrap() * RESOURCE_PRECISION,
+        )
+    }
+
+
+    fn attack(
+        ref self: Troops,
+        ref bravo: Troops,
+        biome: Biome,
+        troop_stamina_config: TroopStaminaConfig,
+        troop_damage_config: TroopDamageConfig,
+        current_tick: u64,
+    ) {
+        let (alpha_damage_dealt, bravo_damage_dealt) = self
+            .damage(ref bravo, biome, troop_stamina_config, troop_damage_config, current_tick);
+
+        let mut alpha = self;
+
         // deduct dead troops from each side
-        alpha.count -= core::cmp::min(alpha.count, BRAVO_DAMAGE_DEALT.round().try_into().unwrap() * RESOURCE_PRECISION);
-        bravo.count -= core::cmp::min(bravo.count, ALPHA_DAMAGE_DEALT.round().try_into().unwrap() * RESOURCE_PRECISION);
+        alpha.count -= core::cmp::min(alpha.count, bravo_damage_dealt);
+        bravo.count -= core::cmp::min(bravo.count, alpha_damage_dealt);
 
         // deduct stamina spent
         alpha
@@ -549,7 +571,7 @@ pub impl TroopsImpl of TroopsTrait {
                 troop_stamina_config,
                 troop_stamina_config.stamina_attack_req.into(),
                 current_tick,
-                false,
+                true,
             );
         bravo
             .stamina
@@ -574,6 +596,7 @@ pub impl TroopsImpl of TroopsTrait {
                     );
             }
             if bravo.count.is_non_zero() {
+                // todo: do we grant stamina here when they originally had none
                 bravo
                     .stamina
                     .add(
@@ -643,9 +666,7 @@ mod tests {
             explorer_guard_max_troop_count: 500_000, // hard max of troops per party
             guard_resurrection_delay: 24 * 60 * 60, // delay in seconds before a guard can be resurrected
             mercenaries_troop_lower_bound: 100_000, // min of troops per mercenary
-            mercenaries_troop_upper_bound: 100_000, // max of troops per mercenary
-            agents_troop_lower_bound: 100_000, // min of troops per agent
-            agents_troop_upper_bound: 100_000 // max of troops per agent
+            mercenaries_troop_upper_bound: 100_000 // max of troops per mercenary
         }
     }
 
diff --git a/contracts/game/src/systems.cairo b/contracts/game/src/systems.cairo
index 1deb34587e6..069ab2127d8 100644
--- a/contracts/game/src/systems.cairo
+++ b/contracts/game/src/systems.cairo
@@ -59,6 +59,7 @@ pub mod combat {
         pub mod troop_battle;
         pub mod troop_management;
         pub mod troop_movement;
+        pub mod troop_raid;
     }
 }
 pub mod bank {
diff --git a/contracts/game/src/systems/combat/contracts/troop_battle.cairo b/contracts/game/src/systems/combat/contracts/troop_battle.cairo
index fbe3e6d28b8..cb5502a49ed 100644
--- a/contracts/game/src/systems/combat/contracts/troop_battle.cairo
+++ b/contracts/game/src/systems/combat/contracts/troop_battle.cairo
@@ -16,7 +16,6 @@ pub trait ITroopBattleSystems<T> {
 #[dojo::contract]
 pub mod troop_battle_systems {
     use core::num::traits::zero::Zero;
-
     use dojo::model::ModelStorage;
     use s1_eternum::alias::ID;
     use s1_eternum::constants::{DAYDREAMS_AGENT_ID, DEFAULT_NS};
@@ -26,13 +25,19 @@ pub mod troop_battle_systems {
     };
     use s1_eternum::models::owner::{OwnerAddressTrait};
     use s1_eternum::models::position::{CoordTrait, Direction};
+    use s1_eternum::models::resource::resource::{ResourceWeightImpl, SingleResourceStoreImpl, WeightStoreImpl};
+    use s1_eternum::models::stamina::{StaminaImpl};
     use s1_eternum::models::structure::{
         StructureBase, StructureBaseImpl, StructureBaseStoreImpl, StructureCategory, StructureOwnerStoreImpl,
         StructureTroopExplorerStoreImpl, StructureTroopGuardStoreImpl,
     };
     use s1_eternum::models::troop::{ExplorerTroops, GuardImpl, GuardSlot, GuardTroops, Troops, TroopsImpl, TroopsTrait};
-    use s1_eternum::systems::utils::{structure::iStructureImpl, troop::{iExplorerImpl, iGuardImpl, iTroopImpl}};
+    use s1_eternum::systems::utils::{
+        resource::{iResourceTransferImpl}, structure::iStructureImpl, troop::{iExplorerImpl, iGuardImpl, iTroopImpl},
+    };
     use s1_eternum::utils::map::biomes::{Biome, get_biome};
+    use s1_eternum::utils::random::{VRFImpl};
+    use super::super::super::super::super::models::troop::GuardTrait;
 
 
     #[abi(embed_v0)]
@@ -316,9 +321,10 @@ pub mod troop_battle_systems {
             let defender_biome: Biome = get_biome(explorer_defender.coord.x.into(), explorer_defender.coord.y.into());
             let troop_damage_config: TroopDamageConfig = CombatConfigImpl::troop_damage_config(ref world);
             let troop_stamina_config: TroopStaminaConfig = CombatConfigImpl::troop_stamina_config(ref world);
+            let mut explorer_defender_troops = explorer_defender.troops;
             structure_guard_aggressor_troops
                 .attack(
-                    ref explorer_defender.troops,
+                    ref explorer_defender_troops,
                     defender_biome,
                     troop_stamina_config,
                     troop_damage_config,
@@ -326,6 +332,7 @@ pub mod troop_battle_systems {
                 );
 
             // update explorer
+            explorer_defender.troops = explorer_defender_troops;
             if explorer_defender.troops.count.is_zero() {
                 if explorer_defender.owner == DAYDREAMS_AGENT_ID {
                     iExplorerImpl::explorer_from_agent_delete(ref world, ref explorer_defender);
diff --git a/contracts/game/src/systems/combat/contracts/troop_raid.cairo b/contracts/game/src/systems/combat/contracts/troop_raid.cairo
new file mode 100644
index 00000000000..e77411d42db
--- /dev/null
+++ b/contracts/game/src/systems/combat/contracts/troop_raid.cairo
@@ -0,0 +1,262 @@
+use s1_eternum::alias::ID;
+use s1_eternum::models::position::{Direction};
+#[starknet::interface]
+pub trait ITroopRaidSystems<T> {
+    fn raid_explorer_vs_guard(
+        ref self: T,
+        explorer_id: ID,
+        structure_id: ID,
+        structure_direction: Direction,
+        steal_resources: Span<(u8, u128)>,
+    );
+}
+
+
+#[dojo::contract]
+pub mod troop_raid_systems {
+    use core::num::traits::zero::Zero;
+
+    use dojo::model::ModelStorage;
+    use s1_eternum::alias::ID;
+    use s1_eternum::constants::{DAYDREAMS_AGENT_ID, DEFAULT_NS, RESOURCE_PRECISION};
+    use s1_eternum::models::config::{
+        BattleConfig, CombatConfigImpl, SeasonConfigImpl, TickImpl, TroopDamageConfig, TroopStaminaConfig,
+        WorldConfigUtilImpl,
+    };
+    use s1_eternum::models::owner::{OwnerAddressTrait};
+    use s1_eternum::models::position::{CoordTrait, Direction};
+    use s1_eternum::models::resource::resource::{ResourceWeightImpl, SingleResourceStoreImpl, WeightStoreImpl};
+    use s1_eternum::models::stamina::{StaminaImpl};
+    use s1_eternum::models::structure::{
+        StructureBase, StructureBaseImpl, StructureBaseStoreImpl, StructureCategory, StructureOwnerStoreImpl,
+        StructureTroopExplorerStoreImpl, StructureTroopGuardStoreImpl,
+    };
+    use s1_eternum::models::troop::{ExplorerTroops, GuardImpl, GuardTroops, TroopsImpl, TroopsTrait};
+    use s1_eternum::models::weight::Weight;
+    use s1_eternum::systems::utils::{
+        resource::{iResourceTransferImpl}, structure::iStructureImpl,
+        troop::{TroopRaidOutcome, iExplorerImpl, iGuardImpl, iTroopImpl},
+    };
+    use s1_eternum::utils::map::biomes::{Biome, get_biome};
+    use s1_eternum::utils::random::{VRFImpl};
+    use s1_eternum::utils::math::{PercentageValueImpl};
+
+    use super::super::super::super::super::models::troop::GuardTrait;
+
+
+    #[abi(embed_v0)]
+    pub impl TroopRaidSystemsImpl of super::ITroopRaidSystems<ContractState> {
+        fn raid_explorer_vs_guard(
+            ref self: ContractState,
+            explorer_id: ID,
+            structure_id: ID,
+            structure_direction: Direction,
+            steal_resources: Span<(u8, u128)>,
+        ) {
+            let mut world = self.world(DEFAULT_NS());
+            SeasonConfigImpl::get(world).assert_started_and_not_over();
+
+            // ensure caller owns aggressor
+            let mut explorer_aggressor: ExplorerTroops = world.read_model(explorer_id);
+            explorer_aggressor.assert_caller_structure_or_agent_owner(ref world);
+
+            // ensure caller does not own defender
+            let mut guarded_structure_owner: starknet::ContractAddress = StructureOwnerStoreImpl::retrieve(
+                ref world, structure_id,
+            );
+            guarded_structure_owner.assert_caller_not_owner();
+
+            // ensure aggressor has troops
+            assert!(explorer_aggressor.troops.count.is_non_zero(), "aggressor has no troops");
+
+            // ensure structure id is for a structure
+            let mut guarded_structure: StructureBase = StructureBaseStoreImpl::retrieve(ref world, structure_id);
+            assert!(guarded_structure.category != StructureCategory::None.into(), "defender is not a structure");
+
+            // ensure structure is not cloaked
+            let tick = TickImpl::get_tick_config(ref world);
+            let battle_config: BattleConfig = WorldConfigUtilImpl::get_member(world, selector!("battle_config"));
+            assert!(StructureBaseImpl::is_not_cloaked(guarded_structure, battle_config, tick), "structure is cloaked");
+
+            // ensure explorer is adjacent to structure
+            assert!(
+                explorer_aggressor.coord.neighbor(structure_direction) == guarded_structure.coord(),
+                "explorer is not adjacent to structure",
+            );
+
+            // get guard troops
+            let mut guard_defender: GuardTroops = StructureTroopGuardStoreImpl::retrieve(ref world, structure_id);
+            let mut explorer_aggressor_troops = explorer_aggressor.troops;
+            let defender_biome: Biome = get_biome(
+                guarded_structure.coord().x.into(), guarded_structure.coord().y.into(),
+            );
+            let troop_damage_config: TroopDamageConfig = CombatConfigImpl::troop_damage_config(ref world);
+            let troop_stamina_config: TroopStaminaConfig = CombatConfigImpl::troop_stamina_config(ref world);
+            let current_tick = tick.current();
+
+            let mut sum_damage_to_explorer = 0;
+            let mut sum_damage_to_guards = 0;
+            let structure_functional_guard_slots = guard_defender
+                .functional_slots(guarded_structure.troop_max_guard_count.into());
+            let mut structure_non_zero_guard_slots = array![];
+            for i in 0..structure_functional_guard_slots.len() {
+                let structure_functional_guard_slot = *structure_functional_guard_slots.at(i);
+                let (mut guard_defender_troops, _) = guard_defender.from_slot(structure_functional_guard_slot);
+                if guard_defender_troops.count.is_non_zero() {
+                    structure_non_zero_guard_slots.append(structure_functional_guard_slot);
+                }
+            };
+
+            if structure_non_zero_guard_slots.len().is_non_zero() {
+                let mut structure_non_zero_guard_slots_damage_dealt = array![];
+                let mut individual_explorer_aggressor_troops = explorer_aggressor_troops;
+                individual_explorer_aggressor_troops.count = explorer_aggressor_troops.count
+                    / structure_non_zero_guard_slots.len().into();
+                individual_explorer_aggressor_troops
+                    .count -= individual_explorer_aggressor_troops
+                    .count % RESOURCE_PRECISION;
+                assert!(
+                    individual_explorer_aggressor_troops.count >= RESOURCE_PRECISION, "not enough troops to pillage",
+                );
+
+                for i in 0..structure_non_zero_guard_slots.len() {
+                    let structure_non_zero_guard_slot = *structure_non_zero_guard_slots.at(i);
+                    let (mut guard_defender_troops, guard_defender_troops_destroyed_tick) = guard_defender
+                        .from_slot(structure_non_zero_guard_slot);
+                    let (damage_dealt_to_guard, damage_dealt_to_explorer) = individual_explorer_aggressor_troops
+                        .damage(
+                            ref guard_defender_troops,
+                            defender_biome,
+                            troop_stamina_config,
+                            troop_damage_config,
+                            current_tick,
+                        );
+
+                    // apply damage to guard slot
+                    let mut guard_damage_applied = troop_damage_config.damage_raid_percent_num.into() * damage_dealt_to_guard / PercentageValueImpl::_100().into();
+                    // add one and make sure it is precise
+                    guard_damage_applied += RESOURCE_PRECISION - (guard_damage_applied % RESOURCE_PRECISION);
+                    guard_defender_troops.count -= core::cmp::min(guard_defender_troops.count, guard_damage_applied);
+
+                    // deduct stamina spent
+                    guard_defender_troops
+                        .stamina
+                        .spend(
+                            guard_defender_troops.category,
+                            troop_stamina_config,
+                            troop_stamina_config.stamina_attack_req.into(),
+                            current_tick,
+                            false,
+                        );
+
+                    // update structure guard
+                    if guard_defender_troops.count.is_zero() {
+                        // delete guard
+                        iGuardImpl::delete(
+                            ref world,
+                            structure_id,
+                            ref guarded_structure,
+                            ref guard_defender,
+                            ref guard_defender_troops,
+                            current_tick.try_into().unwrap(),
+                            *structure_non_zero_guard_slots.at(i),
+                            current_tick,
+                        );
+                    } else {
+                        // update structure guard
+                        guard_defender
+                            .to_slot(
+                                *structure_non_zero_guard_slots.at(i),
+                                guard_defender_troops,
+                                guard_defender_troops_destroyed_tick.into(),
+                            );
+                        StructureTroopGuardStoreImpl::store(ref guard_defender, ref world, structure_id);
+                    }
+
+                    sum_damage_to_explorer += damage_dealt_to_explorer;
+                    sum_damage_to_guards += damage_dealt_to_guard;
+
+                    structure_non_zero_guard_slots_damage_dealt.append(damage_dealt_to_explorer);
+                };
+
+                // apply damage to explorer troops
+                let mut explorer_damage_received = 0;
+                for damage in structure_non_zero_guard_slots_damage_dealt {
+                    // note: damage received by explorer is limited by number of troops
+                    //       used to enter each battle
+                    explorer_damage_received += core::cmp::min(damage, individual_explorer_aggressor_troops.count);
+                };
+
+                let mut explorer_damage_applied = troop_damage_config.damage_raid_percent_num.into() * explorer_damage_received / PercentageValueImpl::_100().into();
+                // add one and make sure it is precise
+                explorer_damage_applied += RESOURCE_PRECISION - (explorer_damage_applied % RESOURCE_PRECISION);
+                explorer_aggressor_troops
+                    .count -= core::cmp::min(explorer_aggressor_troops.count, explorer_damage_applied);
+
+                // deduct stamina spent by explorer
+                explorer_aggressor_troops
+                    .stamina
+                    .spend(
+                        explorer_aggressor_troops.category,
+                        troop_stamina_config,
+                        troop_stamina_config.stamina_attack_req.into(),
+                        current_tick,
+                        true,
+                    );
+
+                // update explorer
+                explorer_aggressor.troops = explorer_aggressor_troops;
+                if explorer_aggressor_troops.count.is_zero() {
+                    if explorer_aggressor.owner == DAYDREAMS_AGENT_ID {
+                        iExplorerImpl::explorer_from_agent_delete(ref world, ref explorer_aggressor);
+                    } else {
+                        let mut explorer_aggressor_owner_structure: StructureBase = StructureBaseStoreImpl::retrieve(
+                            ref world, explorer_aggressor.owner,
+                        );
+                        let mut explorer_aggressor_structure_explorers_list: Array<ID> =
+                            StructureTroopExplorerStoreImpl::retrieve(
+                            ref world, explorer_aggressor.owner,
+                        )
+                            .into();
+                        iExplorerImpl::explorer_from_structure_delete(
+                            ref world,
+                            ref explorer_aggressor,
+                            explorer_aggressor_structure_explorers_list,
+                            ref explorer_aggressor_owner_structure,
+                            explorer_aggressor.owner,
+                        );
+                    }
+                } else {
+                    world.write_model(@explorer_aggressor);
+                }
+            }
+
+            let mut raid_success = true;
+            if structure_non_zero_guard_slots.len().is_non_zero() {
+                let raid_outcome = iTroopImpl::raid_outcome(sum_damage_to_guards, sum_damage_to_explorer);
+                match raid_outcome {
+                    TroopRaidOutcome::Success => { raid_success = true },
+                    TroopRaidOutcome::Failure => { raid_success = false },
+                    TroopRaidOutcome::Chance => {
+                        let vrf_provider: starknet::ContractAddress = WorldConfigUtilImpl::get_member(
+                            world, selector!("vrf_provider_address"),
+                        );
+                        let vrf_seed: u256 = VRFImpl::seed(starknet::get_caller_address(), vrf_provider);
+                        raid_success = iTroopImpl::raid(sum_damage_to_guards, sum_damage_to_explorer, vrf_seed);
+                    },
+                }
+            }
+
+            // steal resources
+            if raid_success {
+                let mut structure_weight: Weight = WeightStoreImpl::retrieve(ref world, structure_id);
+                let mut explorer_weight: Weight = WeightStoreImpl::retrieve(ref world, explorer_id);
+                iResourceTransferImpl::structure_to_troop_instant(
+                    ref world, structure_id, ref structure_weight, explorer_id, ref explorer_weight, steal_resources,
+                );
+                structure_weight.store(ref world, structure_id);
+                explorer_weight.store(ref world, explorer_id);
+            };
+        }
+    }
+}
diff --git a/contracts/game/src/systems/utils/troop.cairo b/contracts/game/src/systems/utils/troop.cairo
index a6a6e95258f..baaf3d510bf 100644
--- a/contracts/game/src/systems/utils/troop.cairo
+++ b/contracts/game/src/systems/utils/troop.cairo
@@ -354,8 +354,36 @@ pub impl iExplorerImpl of iExplorerTrait {
     }
 }
 
+pub enum TroopRaidOutcome {
+    Success,
+    Failure,
+    Chance,
+}
+
 #[generate_trait]
 pub impl iTroopImpl of iTroopTrait {
+    fn raid_outcome(success_weight: u128, failure_weight: u128) -> TroopRaidOutcome {
+        if success_weight > failure_weight * 2 {
+            return TroopRaidOutcome::Success;
+        }
+        if failure_weight > success_weight * 2 {
+            return TroopRaidOutcome::Failure;
+        }
+        return TroopRaidOutcome::Chance;
+    }
+
+    fn raid(success_weight: u128, failure_weight: u128, vrf_seed: u256) -> bool {
+        let success: bool = *random::choices(
+            array![true, false].span(),
+            array![success_weight, failure_weight].span(),
+            array![].span(),
+            1,
+            true,
+            vrf_seed,
+        )[0];
+        return success;
+    }
+
     fn make_payment(
         ref world: WorldStorage, from_structure_id: ID, amount: u128, category: TroopType, tier: TroopTier,
     ) {
diff --git a/packages/core/src/dojo/contract-components.ts b/packages/core/src/dojo/contract-components.ts
index 7c443d836d1..565ec2eaec1 100644
--- a/packages/core/src/dojo/contract-components.ts
+++ b/packages/core/src/dojo/contract-components.ts
@@ -1192,6 +1192,7 @@ export function defineContractComponents(world: World) {
             base_cost_percent_increase: RecsType.Number,
           },
           troop_damage_config: {
+            damage_raid_percent_num: RecsType.Number,
             damage_biome_bonus_num: RecsType.Number,
             damage_beta_small: RecsType.BigInt,
             damage_beta_large: RecsType.BigInt,
@@ -1302,6 +1303,7 @@ export function defineContractComponents(world: World) {
               "u8", // StructureMaxLevelConfig village_max
               "u32", // BuildingConfig base_population
               "u16", // BuildingConfig base_cost_percent_increase
+              "u16", // TroopDamageConfig damage_raid_percent_num
               "u16", // TroopDamageConfig damage_biome_bonus_num
               "u64", // TroopDamageConfig damage_beta_small
               "u64", // TroopDamageConfig damage_beta_large
diff --git a/packages/core/src/provider/index.ts b/packages/core/src/provider/index.ts
index ff6464b379a..a861b89f1eb 100644
--- a/packages/core/src/provider/index.ts
+++ b/packages/core/src/provider/index.ts
@@ -2018,6 +2018,7 @@ export class EternumProvider extends EnhancedDojoProvider {
       entrypoint: "set_troop_config",
       calldata: [
         // damage config
+        damage_config.damage_raid_percent_num,
         damage_config.damage_biome_bonus_num,
         damage_config.damage_beta_small,
         damage_config.damage_beta_large,
diff --git a/packages/core/src/types/common.ts b/packages/core/src/types/common.ts
index 72b928e4853..a22bd80dc5f 100644
--- a/packages/core/src/types/common.ts
+++ b/packages/core/src/types/common.ts
@@ -358,6 +358,7 @@ export interface Config {
       t1DamageValue: bigint;
       t2DamageMultiplier: bigint;
       t3DamageMultiplier: bigint;
+      damageRaidPercentNum: number;
       damageBiomeBonusNum: number;
       damageScalingFactor: bigint;
       damageC0: bigint;
diff --git a/packages/core/src/types/provider.ts b/packages/core/src/types/provider.ts
index c66db3cde19..b2045c14df8 100644
--- a/packages/core/src/types/provider.ts
+++ b/packages/core/src/types/provider.ts
@@ -446,6 +446,7 @@ export interface TroopLimitConfigProps {
 }
 
 export interface TroopDamageConfigProps {
+  damage_raid_percent_num: num.BigNumberish;
   damage_biome_bonus_num: num.BigNumberish;
   damage_beta_small: num.BigNumberish;
   damage_beta_large: num.BigNumberish;
diff --git a/packages/core/src/utils/combat-simulator.ts b/packages/core/src/utils/combat-simulator.ts
index a258b69cec5..b768bca08cb 100644
--- a/packages/core/src/utils/combat-simulator.ts
+++ b/packages/core/src/utils/combat-simulator.ts
@@ -20,6 +20,7 @@ export interface Army {
 }
 
 export interface CombatParameters {
+  damage_raid_percent_num: number;
   damage_biome_bonus_num: number;
   damage_beta_small: bigint;
   damage_beta_large: bigint;
@@ -221,6 +222,7 @@ export class CombatSimulator {
 
   public static getDefaultParameters(): CombatParameters {
     return {
+      damage_raid_percent_num: 1000, //10%
       damage_biome_bonus_num: 3000,
       damage_beta_small: 4611686018427387904n, // 0.25
       damage_beta_large: 2213609288845146193n, // 0.12


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>contracts/game/src/systems.cairo (1)</summary><blockquote>

`62-62`: **Successfully added troop raid module**

The `pub mod troop_raid;` declaration is correctly added to the combat contracts section, following the pattern of existing troop-related modules. 

Consider adding test coverage for this new module, similar to the existing tests for troop battle, management, and movement in the combat test section.

</blockquote></details>
<details>
<summary>contracts/game/src/systems/utils/troop.cairo (1)</summary><blockquote>

`365-373`: **Simple weighted outcome logic**  
The doubling thresholds for success or failure are straightforward. Consider how zero or minimal weights might affect the logic.

</blockquote></details>
<details>
<summary>contracts/game/src/models/troop.cairo (1)</summary><blockquote>

`599-599`: **Clarify the “todo”**  
The comment raises a question about granting stamina to troops that initially had none. If needed, I can help propose a fix or open a new issue.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d54aab2ff0a904702ffd37d52a328c0dbccb9fb5 and bce3339ea08e16444279581be8aa95282834dd02.

</details>

<details>
<summary>📒 Files selected for processing (19)</summary>

* `client/apps/landing/schema.graphql` (1 hunks)
* `client/apps/landing/src/hooks/gql/graphql.ts` (1 hunks)
* `config/deployer/config.ts` (3 hunks)
* `config/environments/_shared_.ts` (2 hunks)
* `config/environments/utils/troop.ts` (1 hunks)
* `contracts/game/dojo_dev.toml` (1 hunks)
* `contracts/game/dojo_local.toml` (1 hunks)
* `contracts/game/dojo_sepolia.toml` (1 hunks)
* `contracts/game/dojo_slot.toml` (1 hunks)
* `contracts/game/src/models/config.cairo` (1 hunks)
* `contracts/game/src/models/troop.cairo` (5 hunks)
* `contracts/game/src/systems.cairo` (1 hunks)
* `contracts/game/src/systems/combat/contracts/troop_battle.cairo` (2 hunks)
* `contracts/game/src/systems/combat/contracts/troop_raid.cairo` (1 hunks)
* `contracts/game/src/systems/utils/troop.cairo` (1 hunks)
* `packages/core/src/dojo/contract-components.ts` (2 hunks)
* `packages/core/src/types/common.ts` (1 hunks)
* `packages/core/src/types/provider.ts` (1 hunks)
* `packages/core/src/utils/combat-simulator.ts` (2 hunks)

</details>

<details>
<summary>👮 Files not reviewed due to content moderation or server errors (1)</summary>

* client/apps/landing/src/hooks/gql/graphql.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>config/environments/_shared_.ts (1)</summary><blockquote>

<details>
<summary>config/environments/utils/troop.ts (1) (1)</summary>

* `TROOP_DAMAGE_RAID_PERCENT_NUM` (26-26)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Actions: test-contracts</summary>

<details>
<summary>contracts/game/src/models/troop.cairo</summary>

[error] 631-631: Missing member 'damage_raid_percent_num'.

---

[error] 664-664: Missing member 'agents_troop_lower_bound'.

---

[error] 664-664: Missing member 'agents_troop_upper_bound'.

</details>
<details>
<summary>contracts/game/src/systems/combat/contracts/troop_raid.cairo</summary>

[error] 1-1: Command failed with exit code 1.

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: test-season-pass

</details>

<details>
<summary>🔇 Additional comments (31)</summary><blockquote>

<details>
<summary>config/environments/utils/troop.ts (1)</summary>

`26-27`: **Clean addition of troop raid damage constant**

The new constant `TROOP_DAMAGE_RAID_PERCENT_NUM` is well-defined with a clear comment indicating it represents 10% damage for raids. The naming follows the existing convention in the file, and the value uses the same basis points representation as other percentage constants.

</details>
<details>
<summary>contracts/game/dojo_dev.toml (1)</summary>

`35-35`: **Successfully added troop raid system to writers list**

The new `s1_eternum-troop_raid_systems` entry is correctly added and appropriately positioned alongside other troop-related systems. Note that there's inconsistent indentation in this section (mix of spaces and tabs), but that's a pre-existing issue not introduced by this change.

</details>
<details>
<summary>contracts/game/dojo_local.toml (1)</summary>

`35-35`: **Successfully added troop raid system to writers list**

The new `s1_eternum-troop_raid_systems` entry is correctly added and appropriately positioned alongside other troop-related systems. As with the dev configuration file, there's inconsistent indentation in this section (mix of spaces and tabs), but that's a pre-existing issue not introduced by this change.

</details>
<details>
<summary>packages/core/src/types/provider.ts (1)</summary>

`448-450`: **Added new damage_raid_percent_num property - LGTM**

The addition of `damage_raid_percent_num` to the `TroopDamageConfigProps` interface is properly typed and follows the existing naming convention of other damage properties in the interface.

</details>
<details>
<summary>contracts/game/dojo_sepolia.toml (1)</summary>

`35-35`: **New troop raid system registration - LGTM**

The addition of `"s1_eternum-troop_raid_systems"` to the writers section is properly placed alongside other troop-related systems.

</details>
<details>
<summary>contracts/game/dojo_slot.toml (1)</summary>

`36-36`: **New troop raid system registration - LGTM**

The addition of `"s1_eternum-troop_raid_systems"` maintains consistency with the changes in dojo_sepolia.toml, ensuring the troop raid functionality is available across environments.

</details>
<details>
<summary>packages/core/src/types/common.ts (1)</summary>

`361-361`: **Added new damageRaidPercentNum property - LGTM**

The addition of `damageRaidPercentNum` to the troop damage configuration follows the existing camelCase naming convention used in the Config interface, and properly corresponds to the snake_case property added in the provider types.

</details>
<details>
<summary>contracts/game/src/models/config.cairo (1)</summary>

`351-351`: **New field for configuring raid damage percentage.**

The addition of `damage_raid_percent_num: u16` introduces the ability to configure the percentage of damage dealt during raids. Based on other numerator fields in the codebase, this likely represents a numerator with an implicit denominator of 10,000, meaning a value of 1,000 would be 10%.

</details>
<details>
<summary>config/environments/_shared_.ts (2)</summary>

`35-35`: **Added import for the new raid damage percentage configuration.**

Good addition of the `TROOP_DAMAGE_RAID_PERCENT_NUM` constant to the imports list. This maintains consistency with the other troop damage parameter imports.

---

`260-260`: <details>
<summary>✅ Verification successful</summary>

**Added raid damage percentage to troop damage configuration.**

The addition of `damageRaidPercentNum` properly integrates the new raid damage percentage parameter into the `EternumGlobalConfig`. This ensures the value is accessible throughout the application.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the TROOP_DAMAGE_RAID_PERCENT_NUM value is referenced properly elsewhere

echo "Checking for references to damageRaidPercentNum in the codebase..."
rg -i "damageRaidPercentNum" --type ts
echo "Checking for references to damage_raid_percent_num in the codebase..."
rg -i "damage_raid_percent_num" --type ts
```

Length of output: 2914

---



</details>

**Integrated raid damage percentage parameter – verified references across the codebase**

The new `damageRaidPercentNum` assigned to `TROOP_DAMAGE_RAID_PERCENT_NUM` in `config/environments/_shared_.ts` is properly integrated. Our search confirms consistent usage:

- It is declared with a numeric type in `packages/core/src/types/common.ts`.
- It bridges with the corresponding `damage_raid_percent_num` in configuration files (e.g., `config/deployer/config.ts`) and utility modules (e.g., `packages/core/src/utils/combat-simulator.ts`).

This verifies that the parameter is accessible globally through the `EternumGlobalConfig` as intended.

</details>
<details>
<summary>packages/core/src/utils/combat-simulator.ts (1)</summary>

`23-23`: **Added raid damage percentage parameter to combat interface.**

The addition of `damage_raid_percent_num` to the `CombatParameters` interface is well-placed. This parameter will allow for configurable raid damage calculations in combat simulations.

</details>
<details>
<summary>client/apps/landing/schema.graphql (1)</summary>

`8741-8751`: **New GraphQL schema fields for troop raid functionality**

These new fields follow the established pattern for filtering capabilities in the GraphQL schema, adding support for querying based on the `damage_raid_percent_num` attribute. This enhancement aligns with the PR's objective to implement raid systems, specifically allowing filtering of troop damage configuration based on raid damage percentages.

</details>
<details>
<summary>packages/core/src/dojo/contract-components.ts (2)</summary>

`1195-1195`: **Looks consistent with the existing pattern**  
No concerns here. The new property is well aligned with the naming convention in the `troop_damage_config`.

---

`1306-1306`: **Type definition for damage_raid_percent_num**  
Defining it as `u16` aligns with the rest of the fields. Make sure all references handle this type correctly.

</details>
<details>
<summary>config/deployer/config.ts (2)</summary>

`520-520`: **Property mapping is consistent**  
The new `damageRaidPercentNum` field is correctly mapped to the updated config object.

---

`602-602`: **Confirm correct percentage representation**  
Ensure that dividing by 10,000 aligns with the intended basis points or decimal precision for `damage_raid_percent_num`.

</details>
<details>
<summary>contracts/game/src/systems/utils/troop.cairo (2)</summary>

`357-362`: **Nice addition of TroopRaidOutcome enum**  
This enum provides clarity in expressing different outcomes, improving code readability.

---

`375-385`: **Random-based raid logic**  
Using `random::choices` to pick success or failure is appropriate. Logging or emitting an event for auditing might be beneficial.

</details>
<details>
<summary>contracts/game/src/models/troop.cairo (4)</summary>

`467-474`: **Refactored damage function to return damage values**  
If raid-specific logic is required, consider incorporating `damage_raid_percent_num` or referencing the new raid outcome flow.

---

`540-547`: **Ensure correct final state**  
Overwriting `self` with `alpha` before returning the damage values is slightly unconventional. Verify this does not conflict with references elsewhere.

---

`549-561`: **New "attack" function invokes the separate "damage" function**  
Extracting the damage calculation into a dedicated method clarifies the flow and improves maintainability.

---

`563-564`: **Troop count deduction**  
Using the `min` function is safe and prevents negative troop counts. This straightforward approach looks correct.

</details>
<details>
<summary>contracts/game/src/systems/combat/contracts/troop_raid.cairo (4)</summary>

`3-12`: **Interface name clarity looks good.**
The naming of `ITroopRaidSystems` and its method `raid_explorer_vs_guard` is self-explanatory, aligning well with the raid logic. No immediate issues with this interface definition.

---

`49-71`: **Comprehensive validations for aggressor and defender ownership.**
You verify the caller's ownership of the explorer aggressor, prohibit ownership conflicts with the defender, and ensure the aggressor has troops. These checks reduce potential exploits.

---

`126-139`: **Confirm rounding approach for troop damage.**
Damage is multiplied by `damage_raid_percent_num` and then rounded up by adding `RESOURCE_PRECISION - (guard_damage_applied % RESOURCE_PRECISION)`. While this ensures non-zero damage, it may cause unexpected rounding for minimal damage amounts. Consider verifying that this aligns with the desired gameplay and user experience.

Would you like to run a quick test on minimal troop counts to see how the rounding affects damage for the edge cases?

---

`250-260`: <details>
<summary>❓ Verification inconclusive</summary>

**Verify no overdraw in resource stealing.**
`iResourceTransferImpl::structure_to_troop_instant` presumably handles partial or zero-sum resources, but there’s no explicit check here. If resources are lower than expected or if the explorer is at capacity, confirm the underlying logic gracefully handles these scenarios.

---

🏁 Script executed:

```shell
#!/bin/bash
# Searching for capacity checks or resource balance logic usage in the codebase:
rg -A 5 'fn structure_to_troop_instant' 
```

Length of output: 533

---



</details>

**Action: Verify Overdraw Protection in Resource Transfer**

The code in `contracts/game/src/systems/combat/contracts/troop_raid.cairo` (lines 250–260) transfers resources via `iResourceTransferImpl::structure_to_troop_instant` without an explicit check for resource levels or explorer capacity. The initial lookup of the function signature in `contracts/game/src/systems/utils/resource.cairo` confirms its interface, but it doesn’t clearly show internal capacity or balance checks.

- Please ensure that `structure_to_troop_instant` gracefully handles scenarios where the structure lacks sufficient resources or when the explorer is at capacity.
- If these cases are already managed internally, consider adding a comment or test case to clarify that no overdraw can occur.

</details>
<details>
<summary>contracts/game/src/systems/combat/contracts/troop_battle.cairo (5)</summary>

`28-29`: **Added imports are consistent with newly introduced resource & stamina functionalities.**
No issues here. Ensure these imports align with usage to avoid unused dependencies warnings or linter flags.

---

`35-37`: **Grouped utility imports provide clarity.**
Consolidating `iResourceTransferImpl`, `iStructureImpl`, `iExplorerImpl`, `iGuardImpl`, and `iTroopImpl` into a single import block is much more readable. Good job.

---

`39-39`: **VRF usage adds fairness to outcomes.**
The new `VRFImpl` import indicates randomness-based logic. Just confirm you’re properly simulating or testing VRF calls to ensure coverage during unit tests.

---

`324-327`: **Explicitly capturing defender troops for the attack phase.**
Assigning `explorer_defender.troops` to a mutable variable before passing it by reference clarifies the logic flow and avoids potential confusion. This is a clean approach.

---

`335-335`: **Reassigning updated troops to the defender ensures state consistency.**
Storing the updated `explorer_defender_troops` back into `explorer_defender` is appropriate. The code properly reflects post-attack troop losses in the explorer's data model.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T22:56:12Z

Review comments:
diff_hunk: @@ -221,6 +222,7 @@ export class CombatSimulator {
 
   public static getDefaultParameters(): CombatParameters {
     return {
+      damage_raid_percent_num: 1000, //10%
path: packages/core/src/utils/combat-simulator.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Added default value for raid damage percentage.**

The default value of 1000 (10%) for `damage_raid_percent_num` is properly set in the `getDefaultParameters` method. The comment clarifying the percentage value is helpful.

However, I notice that while this parameter is added to the interface and default parameters, it's not actually used in the combat simulation logic. The `simulateBattle` method doesn't reference this parameter.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if damage_raid_percent_num is actually used in the combat calculations

echo "Searching for usage of damage_raid_percent_num in simulator logic..."
rg -A 10 -B 10 "damage_raid_percent_num" --type ts packages/core/src/utils/combat-simulator.ts

echo "Checking where this parameter gets initialized in the constructor..."
ast-grep --pattern 'constructor(params: CombatParameters) {
  $$$
}'
```

Length of output: 1695

---



</details>

**Critical: Integrate or Remove Unused `damage_raid_percent_num` Parameter**

The default value of `damage_raid_percent_num` is correctly set (1000 for 10%) in `getDefaultParameters`, and the comment helps clarify its intent. However, our search confirms that this parameter isn’t referenced anywhere in the combat simulation logic (e.g., in the `simulateBattle` method). 

- **Action Needed:**  
  - If the parameter is meant to affect battle outcomes, update the simulation logic to use `damage_raid_percent_num`.
  - Otherwise, consider removing it from the interface and default parameters to avoid confusion.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T22:56:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -643,9 +666,7 @@ mod tests {
             explorer_guard_max_troop_count: 500_000, // hard max of troops per party
             guard_resurrection_delay: 24 * 60 * 60, // delay in seconds before a guard can be resurrected
             mercenaries_troop_lower_bound: 100_000, // min of troops per mercenary
-            mercenaries_troop_upper_bound: 100_000, // max of troops per mercenary
-            agents_troop_lower_bound: 100_000, // min of troops per agent
-            agents_troop_upper_bound: 100_000 // max of troops per agent
+            mercenaries_troop_upper_bound: 100_000 // max of troops per mercenary
path: contracts/game/src/models/troop.cairo
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Pipeline complains about missing agent troop bounds**  
The pipeline indicates “missing member ‘agents_troop_lower_bound’ and ‘agents_troop_upper_bound’.” If these fields are mandatory, ensure they’re added to `TroopLimitConfig` or references are dropped accordingly.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T22:56:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,262 @@
+use s1_eternum::alias::ID;
+use s1_eternum::models::position::{Direction};
+#[starknet::interface]
+pub trait ITroopRaidSystems<T> {
+    fn raid_explorer_vs_guard(
+        ref self: T,
+        explorer_id: ID,
+        structure_id: ID,
+        structure_direction: Direction,
+        steal_resources: Span<(u8, u128)>,
+    );
+}
+
+
+#[dojo::contract]
+pub mod troop_raid_systems {
+    use core::num::traits::zero::Zero;
+
+    use dojo::model::ModelStorage;
+    use s1_eternum::alias::ID;
+    use s1_eternum::constants::{DAYDREAMS_AGENT_ID, DEFAULT_NS, RESOURCE_PRECISION};
+    use s1_eternum::models::config::{
+        BattleConfig, CombatConfigImpl, SeasonConfigImpl, TickImpl, TroopDamageConfig, TroopStaminaConfig,
+        WorldConfigUtilImpl,
+    };
+    use s1_eternum::models::owner::{OwnerAddressTrait};
+    use s1_eternum::models::position::{CoordTrait, Direction};
+    use s1_eternum::models::resource::resource::{ResourceWeightImpl, SingleResourceStoreImpl, WeightStoreImpl};
+    use s1_eternum::models::stamina::{StaminaImpl};
+    use s1_eternum::models::structure::{
+        StructureBase, StructureBaseImpl, StructureBaseStoreImpl, StructureCategory, StructureOwnerStoreImpl,
+        StructureTroopExplorerStoreImpl, StructureTroopGuardStoreImpl,
+    };
+    use s1_eternum::models::troop::{ExplorerTroops, GuardImpl, GuardTroops, TroopsImpl, TroopsTrait};
+    use s1_eternum::models::weight::Weight;
+    use s1_eternum::systems::utils::{
+        resource::{iResourceTransferImpl}, structure::iStructureImpl,
+        troop::{TroopRaidOutcome, iExplorerImpl, iGuardImpl, iTroopImpl},
+    };
+    use s1_eternum::utils::map::biomes::{Biome, get_biome};
+    use s1_eternum::utils::random::{VRFImpl};
+    use s1_eternum::utils::math::{PercentageValueImpl};
+
+    use super::super::super::super::super::models::troop::GuardTrait;
+
+
+    #[abi(embed_v0)]
+    pub impl TroopRaidSystemsImpl of super::ITroopRaidSystems<ContractState> {
+        fn raid_explorer_vs_guard(
+            ref self: ContractState,
+            explorer_id: ID,
+            structure_id: ID,
+            structure_direction: Direction,
+            steal_resources: Span<(u8, u128)>,
+        ) {
+            let mut world = self.world(DEFAULT_NS());
+            SeasonConfigImpl::get(world).assert_started_and_not_over();
+
+            // ensure caller owns aggressor
+            let mut explorer_aggressor: ExplorerTroops = world.read_model(explorer_id);
+            explorer_aggressor.assert_caller_structure_or_agent_owner(ref world);
+
+            // ensure caller does not own defender
+            let mut guarded_structure_owner: starknet::ContractAddress = StructureOwnerStoreImpl::retrieve(
+                ref world, structure_id,
+            );
+            guarded_structure_owner.assert_caller_not_owner();
+
+            // ensure aggressor has troops
+            assert!(explorer_aggressor.troops.count.is_non_zero(), "aggressor has no troops");
+
+            // ensure structure id is for a structure
+            let mut guarded_structure: StructureBase = StructureBaseStoreImpl::retrieve(ref world, structure_id);
+            assert!(guarded_structure.category != StructureCategory::None.into(), "defender is not a structure");
+
+            // ensure structure is not cloaked
+            let tick = TickImpl::get_tick_config(ref world);
+            let battle_config: BattleConfig = WorldConfigUtilImpl::get_member(world, selector!("battle_config"));
+            assert!(StructureBaseImpl::is_not_cloaked(guarded_structure, battle_config, tick), "structure is cloaked");
+
+            // ensure explorer is adjacent to structure
+            assert!(
+                explorer_aggressor.coord.neighbor(structure_direction) == guarded_structure.coord(),
+                "explorer is not adjacent to structure",
+            );
+
+            // get guard troops
+            let mut guard_defender: GuardTroops = StructureTroopGuardStoreImpl::retrieve(ref world, structure_id);
+            let mut explorer_aggressor_troops = explorer_aggressor.troops;
+            let defender_biome: Biome = get_biome(
+                guarded_structure.coord().x.into(), guarded_structure.coord().y.into(),
+            );
+            let troop_damage_config: TroopDamageConfig = CombatConfigImpl::troop_damage_config(ref world);
+            let troop_stamina_config: TroopStaminaConfig = CombatConfigImpl::troop_stamina_config(ref world);
+            let current_tick = tick.current();
+
+            let mut sum_damage_to_explorer = 0;
+            let mut sum_damage_to_guards = 0;
+            let structure_functional_guard_slots = guard_defender
+                .functional_slots(guarded_structure.troop_max_guard_count.into());
+            let mut structure_non_zero_guard_slots = array![];
+            for i in 0..structure_functional_guard_slots.len() {
+                let structure_functional_guard_slot = *structure_functional_guard_slots.at(i);
+                let (mut guard_defender_troops, _) = guard_defender.from_slot(structure_functional_guard_slot);
+                if guard_defender_troops.count.is_non_zero() {
+                    structure_non_zero_guard_slots.append(structure_functional_guard_slot);
+                }
+            };
+
+            if structure_non_zero_guard_slots.len().is_non_zero() {
+                let mut structure_non_zero_guard_slots_damage_dealt = array![];
+                let mut individual_explorer_aggressor_troops = explorer_aggressor_troops;
+                individual_explorer_aggressor_troops.count = explorer_aggressor_troops.count
+                    / structure_non_zero_guard_slots.len().into();
+                individual_explorer_aggressor_troops
+                    .count -= individual_explorer_aggressor_troops
+                    .count % RESOURCE_PRECISION;
+                assert!(
+                    individual_explorer_aggressor_troops.count >= RESOURCE_PRECISION, "not enough troops to pillage",
+                );
+
+                for i in 0..structure_non_zero_guard_slots.len() {
+                    let structure_non_zero_guard_slot = *structure_non_zero_guard_slots.at(i);
+                    let (mut guard_defender_troops, guard_defender_troops_destroyed_tick) = guard_defender
+                        .from_slot(structure_non_zero_guard_slot);
+                    let (damage_dealt_to_guard, damage_dealt_to_explorer) = individual_explorer_aggressor_troops
+                        .damage(
+                            ref guard_defender_troops,
+                            defender_biome,
+                            troop_stamina_config,
+                            troop_damage_config,
+                            current_tick,
+                        );
+
+                    // apply damage to guard slot
+                    let mut guard_damage_applied = troop_damage_config.damage_raid_percent_num.into() * damage_dealt_to_guard / PercentageValueImpl::_100().into();
+                    // add one and make sure it is precise
+                    guard_damage_applied += RESOURCE_PRECISION - (guard_damage_applied % RESOURCE_PRECISION);
+                    guard_defender_troops.count -= core::cmp::min(guard_defender_troops.count, guard_damage_applied);
+
+                    // deduct stamina spent
+                    guard_defender_troops
+                        .stamina
+                        .spend(
+                            guard_defender_troops.category,
+                            troop_stamina_config,
+                            troop_stamina_config.stamina_attack_req.into(),
+                            current_tick,
+                            false,
+                        );
+
+                    // update structure guard
+                    if guard_defender_troops.count.is_zero() {
+                        // delete guard
+                        iGuardImpl::delete(
+                            ref world,
+                            structure_id,
+                            ref guarded_structure,
+                            ref guard_defender,
+                            ref guard_defender_troops,
+                            current_tick.try_into().unwrap(),
+                            *structure_non_zero_guard_slots.at(i),
+                            current_tick,
+                        );
+                    } else {
+                        // update structure guard
+                        guard_defender
+                            .to_slot(
+                                *structure_non_zero_guard_slots.at(i),
+                                guard_defender_troops,
+                                guard_defender_troops_destroyed_tick.into(),
+                            );
+                        StructureTroopGuardStoreImpl::store(ref guard_defender, ref world, structure_id);
+                    }
+
+                    sum_damage_to_explorer += damage_dealt_to_explorer;
+                    sum_damage_to_guards += damage_dealt_to_guard;
+
+                    structure_non_zero_guard_slots_damage_dealt.append(damage_dealt_to_explorer);
+                };
+
+                // apply damage to explorer troops
+                let mut explorer_damage_received = 0;
+                for damage in structure_non_zero_guard_slots_damage_dealt {
+                    // note: damage received by explorer is limited by number of troops
+                    //       used to enter each battle
+                    explorer_damage_received += core::cmp::min(damage, individual_explorer_aggressor_troops.count);
+                };
+
+                let mut explorer_damage_applied = troop_damage_config.damage_raid_percent_num.into() * explorer_damage_received / PercentageValueImpl::_100().into();
+                // add one and make sure it is precise
+                explorer_damage_applied += RESOURCE_PRECISION - (explorer_damage_applied % RESOURCE_PRECISION);
+                explorer_aggressor_troops
path: contracts/game/src/systems/combat/contracts/troop_raid.cairo
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Double-check rounding for explorer damage.**
Similar to guard damage, the explorer’s damage is also rounded up. This can result in partial damage leaps for small troop counts. Ensure that these increments won’t frustrate players by causing disproportionate losses.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T22:56:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-03-19T22:45:18Z
body: [vc]: #mUlPn8PPpncNmVxfSZfN1HxX/F++OHb5g9rL6oa6Kpw=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJldGVybnVtIiwicm9vdERpcmVjdG9yeSI6bnVsbCwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xvb3QtYmlibGlvdGhlY2EvZXRlcm51bS83c3pEeUNnRWk0YlJYVTZxMXBKb0FmTFFCRVFwIiwicHJldmlld1VybCI6IiIsIm5leHRDb21taXRTdGF0dXMiOiJGQUlMRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn19LHsibmFtZSI6ImV0ZXJudW0tbGFuZGluZyIsInJvb3REaXJlY3RvcnkiOm51bGwsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9sb290LWJpYmxpb3RoZWNhL2V0ZXJudW0tbGFuZGluZy9aYm5xbnppWkJiaFhTMVNYenhWWEZXanFpemZoIiwicHJldmlld1VybCI6ImV0ZXJudW0tbGFuZGluZy1naXQtZmVhdC10cm9vcC1yYWlkLWxvb3QtYmlibGlvdGhlY2EudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9fSx7Im5hbWUiOiJldGVybnVtLWRvY3MiLCJyb290RGlyZWN0b3J5IjpudWxsLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbG9vdC1iaWJsaW90aGVjYS9ldGVybnVtLWRvY3MvRnBjcHFVTDNnWDlTSkNmVFpxM3hCTDlDdGhFOSIsInByZXZpZXdVcmwiOiIiLCJuZXh0Q29tbWl0U3RhdHVzIjoiRkFJTEVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9fSx7Im5hbWUiOiJldGVybnVtLW1vYmlsZSIsInJvb3REaXJlY3RvcnkiOm51bGwsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9sb290LWJpYmxpb3RoZWNhL2V0ZXJudW0tbW9iaWxlL0dzQVk0M3pBYzJMdnQ4b2lYMlg5RkpkWlJnNkwiLCJwcmV2aWV3VXJsIjoiZXRlcm51bS1tb2JpbGUtZ2l0LWZlYXQtdHJvb3AtcmFpZC1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZXRlcm51bS1tb2JpbGUtZ2l0LWZlYXQtdHJvb3AtcmFpZC1sb290LWJpYmxpb3RoZWNhLnZlcmNlbC5hcHAifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **eternum** | ❌ Failed ([Inspect](https://vercel.com/loot-bibliotheca/eternum/7szDyCgEi4bRXU6q1pJoAfLQBEQp)) |  |  | Mar 20, 2025 8:08am |
| **eternum-docs** | ❌ Failed ([Inspect](https://vercel.com/loot-bibliotheca/eternum-docs/FpcpqUL3gX9SJCfTZq3xBL9CthE9)) |  |  | Mar 20, 2025 8:08am |
| **eternum-mobile** | ✅ Ready ([Inspect](https://vercel.com/loot-bibliotheca/eternum-mobile/GsAY43zAc2Lvt8oiX2X9FJdZRg6L)) | [Visit Preview](https://eternum-mobile-git-feat-troop-raid-loot-bibliotheca.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eternum-mobile-git-feat-troop-raid-loot-bibliotheca.vercel.app?via=pr-comment-feedback-link) | Mar 20, 2025 8:08am |

<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **eternum-landing** | ⬜️ Ignored ([Inspect](https://vercel.com/loot-bibliotheca/eternum-landing/ZbnqnziZBbhXS1SXzxVXFWjqizfh)) | [Visit Preview](https://eternum-landing-git-feat-troop-raid-loot-bibliotheca.vercel.app) |  | Mar 20, 2025 8:08am |
</details>





author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-19T22:45:21Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces a series of new fields and properties for troop damage configuration and raid systems across multiple modules. Changes span GraphQL schemas, configuration files, smart contract modules, and utility functions. The updates add filtering options for raid damage percentages, refactor the attack logic to separate damage calculations, and implement comprehensive raid outcome handling with new enums and methods. Additionally, new modules and configuration entries related to troop raids have been incorporated throughout the system.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `client/apps/landing/schema.graphql`<br>`client/apps/landing/src/hooks/gql/graphql.ts` | Added new filtering fields (e.g., `damage_raid_percent_num`, `damage_raid_percent_numEQ`, etc.) for troop damage configuration in GraphQL input types. |
| `config/deployer/config.ts`<br>`config/environments/_shared_.ts`<br>`config/environments/utils/troop.ts` | Introduced new properties/constants (`damageRaidPercentNum`, `TROOP_DAMAGE_RAID_PERCENT_NUM`) to extend troop damage configurations and adjust output formatting. |
| `contracts/game/dojo_dev.toml`<br>`contracts/game/dojo_local.toml`<br>`contracts/game/dojo_sepolia.toml`<br>`contracts/game/dojo_slot.toml` | Added new entry `"s1_eternum-troop_raid_systems"` under the `[writers]` section to enable troop raid modules. |
| `contracts/game/src/models/config.cairo` | Added new public field `damage_raid_percent_num: u16` to the `TroopDamageConfig` struct. |
| `contracts/game/src/models/troop.cairo` | Refactored troop attack logic by renaming the original `attack` function to `damage` (now returning `(u128, u128)`) and adding a new `attack` function that calls `damage`. |
| `contracts/game/src/systems.cairo`<br>`contracts/game/src/systems/combat/contracts/troop_raid.cairo` | Introduced a new `troop_raid` module with a corresponding interface and implementation for managing troop raids. |
| `contracts/game/src/systems/combat/contracts/troop_battle.cairo`<br>`contracts/game/src/systems/utils/troop.cairo` | Enhanced combat systems with new imports, and added a `TroopRaidOutcome` enum and methods (`raid_outcome`, `raid`) for determining raid outcomes. |
| `packages/core/src/dojo/contract-components.ts`<br>`packages/core/src/types/common.ts`<br>`packages/core/src/types/provider.ts`<br>`packages/core/src/utils/combat-simulator.ts` | Extended configuration types and defaults by adding the `damage_raid_percent_num` property and updating CombatSimulator default parameters. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant E as Explorer
    participant TR as TroopRaidSystemsImpl
    participant S as Structure/Guard
    participant VRF as Randomizer

    E->>TR: Call raid_explorer_vs_guard(explorer_id, structure_id, ...)
    TR->>TR: Validate ownership, troop availability, and adjacency
    TR->>S: Retrieve guard troop data
    TR->>TR: Compute damage using internal damage() function
    TR->>VRF: Request random outcome for raid chance
    VRF-->>TR: Return success/failure result
    TR->>S: Update guard troop status and resources
    TR->>E: Apply damage, update stamina, and return raid outcome
```

```mermaid
sequenceDiagram
    participant G as Game Engine
    participant T as TroopsImpl

    G->>T: Invoke attack() function
    T->>T: Call internal damage() to calculate outcomes
    T-->>G: Return damage values (attacker, defender)
    G->>T: Update troop counts based on returned damage
```

## Possibly related PRs
- **BibliothecaDAO/eternum#2101:** Introduces new fields for troop damage configuration, closely aligning with the current modifications.
  
## Suggested reviewers
- ponderingdemocritus
- edisontim

## Poem
> In the code garden I hop with delight,  
> Adding fields and functions, day and night.  
> Troop raids and damage now dance in the light,  
> Each module enhanced—a marvelous sight!  
> With a twitch of my nose, I celebrate the bytes.  
> Happy coding, as I bound on in joyful flights!

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>packages/core/src/provider/index.ts</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.22.0
> 
> ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
> 
> From ESLint v9.0.0, the default configuration file is now eslint.config.js.
> If you are using a .eslintrc.* file, please follow the migration guide
> to update your configuration file to the new format:
> 
> https://eslint.org/docs/latest/use/configure/migration-guide
> 
> If you still have problems after following the migration guide, please stop by
> https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between bce3339ea08e16444279581be8aa95282834dd02 and 7c5b3d2cb439771832ba749d0bca25e0f39e46c0.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/core/src/provider/index.ts` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: test-season-pass

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>packages/core/src/provider/index.ts (1)</summary>

`2020-2022`: **New parameter added for troop raid damage configuration.**

The addition of `damage_config.damage_raid_percent_num` to the calldata array aligns with the PR's objective of introducing features for troop raids. This parameter will allow configuring the raid damage percentage for troops.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=BibliothecaDAO/eternum&utm_content=2779):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-19T22:47:12Z
body: Failed to generate code suggestions for PR
author_association: CONTRIBUTOR