Would it make sense to split up any of our rust source files into multiple files to make them more understandable or easier to read?
The user is asking about whether any Rust source files in the mtg-rl workspace should be split up for readability. This is a research/analysis question about code organization. Let me explore the codebase to understand file sizes and complexity before making recommendations.
Let me check if any skills apply - this is an analysis/research question, not really a creative task or implementation task. The brainstorming skill says "before any creative work - creating features, building components, adding functionality, or modifying behavior." This is just analysis, so I don't think brainstorming applies. No other skills seem to apply either.
Let me explore the codebase to find large files that might benefit from splitting.
Let me survey the Rust source files to identify candidates based on size and complexity.
find /mnt/dest-btrfs/home/jc/src/mage/mtg-rl -name '*.rs' -not -path '*/target/*' -exec wc -l {} + | sort -rn | head -40 51736 total
13828 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs
7576 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs
4761 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs
4424 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/tla.rs
4287 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/tdm.rs
2113 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs
907 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/constants.rs
903 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-tests/src/framework.rs
841 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/state.rs
830 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/zones.rs
773 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/events.rs
647 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/combat.rs
584 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai/src/minimax_player.rs
565 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/watchers.rs
541 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/effects.rs
516 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/filters.rs
501 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai/src/evaluator.rs
491 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/permanent.rs
481 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/mana.rs
475 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai/src/heuristic_player.rs
447 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai/src/action_space.rs
439 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/registry.rs
415 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/targets.rs
391 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/turn.rs
384 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai/src/observation.rs
357 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/decision.rs
335 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai/src/random_player.rs
332 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/player.rs
327 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/mana_pool.rs
324 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-tests/src/scripted_player.rs
324 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/counters.rs
298 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai/src/gym.rs
276 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-tests/src/concurrency.rs
250 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-tests/benches/game_bench.rs
201 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/card.rs
114 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-python/src/lib.rs
92 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/keywords/mobilize.rs
86 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/cards/basic_lands.rs
78 /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/types.rsThe clear standout is game.rs at nearly 14K lines. Let me look at its internal structure to understand what could be split.
grep -n '^\s*pub fn\|^\s*fn\|^\s*impl\|^\s*// ==\|^\s*/// #\|^\s*mod ' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs | head -10092:impl Game {
94: pub fn new_two_player(
160: pub fn run(&mut self) -> GameResult {
248: fn london_mulligan(&mut self, player_ids: &[PlayerId]) {
349: fn process_step(&mut self) {
370: fn process_sba_and_triggers(&mut self) {
408: fn apply_continuous_effects(&mut self) {
682: fn evaluate_condition(&self, source_id: ObjectId, controller: PlayerId, condition: &str) -> bool {
726: fn evaluate_count_filter(&self, filter: &str, controller: PlayerId) -> u32 {
792: pub fn calculate_cost_reduction(&self, player_id: PlayerId, card: &crate::card::CardData) -> u32 {
816: fn spell_matches_cost_filter(&self, card: &crate::card::CardData, filter: &str) -> bool {
861: fn find_matching_permanents(
923: fn check_enters_tapped(&mut self, permanent_id: ObjectId) {
943: fn check_triggered_abilities(&mut self) -> bool {
1186: fn emit_event(&mut self, event: GameEvent) {
1191: fn turn_based_actions(&mut self, step: PhaseStep, active_player: PlayerId) {
1330: fn declare_attackers_step(&mut self, active_player: PlayerId) {
1421: fn declare_blockers_step(&mut self, _active_player: PlayerId) {
1551: fn combat_damage_step(&mut self, is_first_strike: bool) {
1636: fn priority_loop(&mut self) {
1721: fn compute_legal_actions(&self, player_id: PlayerId) -> Vec<crate::decision::PlayerAction> {
1895: fn play_land(&mut self, player_id: PlayerId, card_id: ObjectId) {
1939: fn cast_spell(&mut self, player_id: PlayerId, card_id: ObjectId) {
2070: fn check_ward_on_targets(&mut self, spell_id: ObjectId, caster: PlayerId) {
2106: fn find_ward_cost(&self, permanent_id: ObjectId) -> Option<String> {
2120: fn try_pay_ward_cost(&mut self, payer: PlayerId, cost: &str) -> bool {
2170: fn resolve_top_of_stack(&mut self) {
2263: pub fn process_state_based_actions(&mut self) {
2274: fn apply_state_based_actions(&mut self, sba: &StateBasedActions) -> Vec<ObjectId> {
2361: fn activate_ability(
2396: fn activate_mana_ability(
2427: fn count_colors_among_permanents(&self, player_id: PlayerId) -> usize {
2440: fn can_pay_additional_costs(&self, player_id: PlayerId, source_id: ObjectId, costs: &[Cost]) -> bool {
2496: fn pay_costs(&mut self, player_id: PlayerId, source_id: ObjectId, costs: &[Cost]) -> bool {
2823: pub fn execute_effects(&mut self, effects: &[Effect], controller: PlayerId, all_targets: &[ObjectId], source: Option<ObjectId>, x_value: Option<u32>) {
4460: fn move_card_to_graveyard_inner(&mut self, card_id: ObjectId, owner: PlayerId) {
4468: fn move_card_to_graveyard(&mut self, card_id: ObjectId, owner: PlayerId) {
4476: pub fn draw_cards(&mut self, player_id: PlayerId, count: u32) {
4503: fn parse_token_stats(token_name: &str) -> (i32, i32, crate::constants::KeywordAbilities) {
4549: fn matches_filter(perm: &Permanent, filter: &str) -> bool {
4593: fn card_matches_filter(card: &CardData, filter: &str) -> bool {
4624: fn select_targets_for_spec(
4674: fn legal_targets_for_spec(
4745: fn is_untargetable(perm: &Permanent, targeting_controller: PlayerId) -> bool {
4758: fn target_spec_description(spec: &crate::abilities::TargetSpec) -> String {
4773: fn target_spec_outcome(spec: &crate::abilities::TargetSpec) -> crate::constants::Outcome {
4789: fn resolve_fight_pair(
4833: fn check_game_end(&self) -> Option<GameResult> {
4857:mod tests {
4871: impl PlayerDecisionMaker for AlwaysPassPlayer {
4872: fn priority(&mut self, _game: &GameView<'_>, _legal: &[PlayerAction]) -> PlayerAction {
4875: fn choose_targets(&mut self, _: &GameView<'_>, _: Outcome, _: &TargetRequirement) -> Vec<ObjectId> { vec![] }
4876: fn choose_use(&mut self, _: &GameView<'_>, _: Outcome, _: &str) -> bool { false }
4877: fn choose_mode(&mut self, _: &GameView<'_>, _: &[NamedChoice]) -> usize { 0 }
4878: fn select_attackers(&mut self, _: &GameView<'_>, _: &[ObjectId], _: &[ObjectId]) -> Vec<(ObjectId, ObjectId)> { vec![] }
4879: fn select_blockers(&mut self, _: &GameView<'_>, _: &[AttackerInfo]) -> Vec<(ObjectId, ObjectId)> { vec![] }
4880: fn assign_damage(&mut self, _: &GameView<'_>, _: &DamageAssignment) -> Vec<(ObjectId, u32)> { vec![] }
4881: fn choose_mulligan(&mut self, _: &GameView<'_>, _: &[ObjectId]) -> bool { false }
4882: fn choose_cards_to_put_back(&mut self, _: &GameView<'_>, _: &[ObjectId], _: usize) -> Vec<ObjectId> { vec![] }
4883: fn choose_discard(&mut self, _: &GameView<'_>, _: &[ObjectId], _: usize) -> Vec<ObjectId> { vec![] }
4884: fn choose_amount(&mut self, _: &GameView<'_>, _: &str, min: u32, _: u32) -> u32 { min }
4885: fn choose_mana_payment(&mut self, _: &GameView<'_>, _: &UnpaidMana, _: &[PlayerAction]) -> Option<PlayerAction> { None }
4886: fn choose_replacement_effect(&mut self, _: &GameView<'_>, _: &[ReplacementEffectChoice]) -> usize { 0 }
4887: fn choose_pile(&mut self, _: &GameView<'_>, _: Outcome, _: &str, _: &[ObjectId], _: &[ObjectId]) -> bool { true }
4888: fn choose_option(&mut self, _: &GameView<'_>, _: Outcome, _: &str, _: &[NamedChoice]) -> usize { 0 }
4891: fn make_basic_land(name: &str, owner: PlayerId) -> CardData {
4897: fn make_creature(name: &str, owner: PlayerId, power: i32, toughness: i32) -> CardData {
4906: fn make_deck(owner: PlayerId) -> Vec<CardData> {
4920: fn game_creation() {
4949: fn game_runs_to_completion() {
4977: fn draw_cards_from_empty_library_causes_loss() {
5010: fn counter_annihilation_applied() {
5050: fn legend_rule_applied() {
5101: fn legal_actions_include_pass() {
5126: fn mana_ability_and_spell_cast() {
5179: fn activated_ability_goes_on_stack() {
5242: fn spell_effects_execute_on_resolve() {
5307: fn fizzle_when_target_removed() {
5369: fn draw_cards_effect() {
5403: fn gain_life_effect() {
5428: fn lose_life_opponents_effect() {
5456: fn exile_effect() {
5493: fn bounce_effect() {
5531: fn pay_costs_tap_and_sacrifice() {
5578: fn add_counters_self_when_no_targets() {
5631: fn add_counters_self_with_separate_target() {
5697: impl PlayerDecisionMaker for DiscardingPlayer {
5698: fn priority(&mut self, _game: &GameView<'_>, _legal: &[PlayerAction]) -> PlayerAction {
5701: fn choose_targets(&mut self, _: &GameView<'_>, _: Outcome, _: &TargetRequirement) -> Vec<ObjectId> { vec![] }
5702: fn choose_use(&mut self, _: &GameView<'_>, _: Outcome, _: &str) -> bool { false }
5703: fn choose_mode(&mut self, _: &GameView<'_>, _: &[NamedChoice]) -> usize { 0 }
5704: fn select_attackers(&mut self, _: &GameView<'_>, _: &[ObjectId], _: &[ObjectId]) -> Vec<(ObjectId, ObjectId)> { vec![] }
5705: fn select_blockers(&mut self, _: &GameView<'_>, _: &[AttackerInfo]) -> Vec<(ObjectId, ObjectId)> { vec![] }
5706: fn assign_damage(&mut self, _: &GameView<'_>, _: &DamageAssignment) -> Vec<(ObjectId, u32)> { vec![] }
5707: fn choose_mulligan(&mut self, _: &GameView<'_>, _: &[ObjectId]) -> bool { false }
5708: fn choose_cards_to_put_back(&mut self, _: &GameView<'_>, _: &[ObjectId], _: usize) -> Vec<ObjectId> { vec![] }
5709: fn choose_discard(&mut self, _: &GameView<'_>, hand: &[ObjectId], count: usize) -> Vec<ObjectId> {
5713: fn choose_amount(&mut self, _: &GameView<'_>, _: &str, min: u32, _: u32) -> u32 { min }
5714: fn choose_mana_payment(&mut self, _: &GameView<'_>, _: &UnpaidMana, _: &[PlayerAction]) -> Option<PlayerAction> { None }
5715: fn choose_replacement_effect(&mut self, _: &GameView<'_>, _: &[ReplacementEffectChoice]) -> usize { 0 }grep -n '^\s*pub fn\|^\s*fn\|^\s*impl\|^\s*// ==\|^\s*/// #\|^\s*mod ' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs | tail -6013315: fn put_from_hand_noncreature_not_eligible() {
13336: fn effect_builder_put_from_hand_haste_sacrifice() {
13351: fn effect_builder_put_from_hand_tapped_attacking() {
13367:mod cant_untap_tests {
13380: impl PlayerDecisionMaker for AlwaysPassPlayer {
13381: fn priority(&mut self, _: &GameView<'_>, _: &[PlayerAction]) -> PlayerAction {
13384: fn choose_targets(&mut self, _: &GameView<'_>, _: Outcome, _: &TargetRequirement) -> Vec<ObjectId> { vec![] }
13385: fn choose_use(&mut self, _: &GameView<'_>, _: Outcome, _: &str) -> bool { false }
13386: fn choose_mode(&mut self, _: &GameView<'_>, _: &[NamedChoice]) -> usize { 0 }
13387: fn select_attackers(&mut self, _: &GameView<'_>, _: &[ObjectId], _: &[ObjectId]) -> Vec<(ObjectId, ObjectId)> { vec![] }
13388: fn select_blockers(&mut self, _: &GameView<'_>, _: &[AttackerInfo]) -> Vec<(ObjectId, ObjectId)> { vec![] }
13389: fn assign_damage(&mut self, _: &GameView<'_>, _: &DamageAssignment) -> Vec<(ObjectId, u32)> { vec![] }
13390: fn choose_mulligan(&mut self, _: &GameView<'_>, _: &[ObjectId]) -> bool { false }
13391: fn choose_cards_to_put_back(&mut self, _: &GameView<'_>, _: &[ObjectId], _: usize) -> Vec<ObjectId> { vec![] }
13392: fn choose_discard(&mut self, _: &GameView<'_>, _: &[ObjectId], _: usize) -> Vec<ObjectId> { vec![] }
13393: fn choose_amount(&mut self, _: &GameView<'_>, _: &str, min: u32, _: u32) -> u32 { min }
13394: fn choose_mana_payment(&mut self, _: &GameView<'_>, _: &UnpaidMana, _: &[PlayerAction]) -> Option<PlayerAction> { None }
13395: fn choose_replacement_effect(&mut self, _: &GameView<'_>, _: &[ReplacementEffectChoice]) -> usize { 0 }
13396: fn choose_pile(&mut self, _: &GameView<'_>, _: Outcome, _: &str, _: &[ObjectId], _: &[ObjectId]) -> bool { true }
13397: fn choose_option(&mut self, _: &GameView<'_>, _: Outcome, _: &str, _: &[NamedChoice]) -> usize { 0 }
13400: fn make_basic_land(name: &str, owner: PlayerId) -> CardData {
13406: fn make_creature(name: &str, owner: PlayerId, power: i32, toughness: i32) -> CardData {
13414: fn make_deck(owner: PlayerId) -> Vec<CardData> {
13425: fn setup_game() -> (Game, PlayerId, PlayerId) {
13446: fn cant_untap_prevents_untap_during_untap_step() {
13465: fn normal_creature_untaps_during_untap_step() {
13483: fn cant_untap_static_effect_sets_flag_on_enchanted_creature() {
13516: fn cant_untap_aura_prevents_untapping() {
13551: fn removing_cant_untap_aura_allows_untapping() {
13598: fn cant_untap_self_filter() {
13623: fn cant_untap_only_affects_matching_permanents() {
13667: fn static_effect_helper_constructor() {
13678:mod choose_type_reanimate_tests {
13690: impl PlayerDecisionMaker for OptionPicker {
13691: fn priority(&mut self, _: &GameView<'_>, _: &[PlayerAction]) -> PlayerAction {
13694: fn choose_targets(&mut self, _: &GameView<'_>, _: Outcome, _: &TargetRequirement) -> Vec<ObjectId> { vec![] }
13695: fn choose_use(&mut self, _: &GameView<'_>, _: Outcome, _: &str) -> bool { false }
13696: fn choose_mode(&mut self, _: &GameView<'_>, _: &[NamedChoice]) -> usize { 0 }
13697: fn select_attackers(&mut self, _: &GameView<'_>, _: &[ObjectId], _: &[ObjectId]) -> Vec<(ObjectId, ObjectId)> { vec![] }
13698: fn select_blockers(&mut self, _: &GameView<'_>, _: &[AttackerInfo]) -> Vec<(ObjectId, ObjectId)> { vec![] }
13699: fn assign_damage(&mut self, _: &GameView<'_>, _: &DamageAssignment) -> Vec<(ObjectId, u32)> { vec![] }
13700: fn choose_mulligan(&mut self, _: &GameView<'_>, _: &[ObjectId]) -> bool { false }
13701: fn choose_cards_to_put_back(&mut self, _: &GameView<'_>, _: &[ObjectId], _: usize) -> Vec<ObjectId> { vec![] }
13702: fn choose_discard(&mut self, _: &GameView<'_>, _: &[ObjectId], _: usize) -> Vec<ObjectId> { vec![] }
13703: fn choose_amount(&mut self, _: &GameView<'_>, _: &str, min: u32, _: u32) -> u32 { min }
13704: fn choose_mana_payment(&mut self, _: &GameView<'_>, _: &UnpaidMana, _: &[PlayerAction]) -> Option<PlayerAction> { None }
13705: fn choose_replacement_effect(&mut self, _: &GameView<'_>, _: &[ReplacementEffectChoice]) -> usize { 0 }
13706: fn choose_pile(&mut self, _: &GameView<'_>, _: Outcome, _: &str, _: &[ObjectId], _: &[ObjectId]) -> bool { true }
13707: fn choose_option(&mut self, _: &GameView<'_>, _: Outcome, _: &str, _: &[NamedChoice]) -> usize {
13712: fn make_deck(owner: PlayerId) -> Vec<CardData> {
13720: fn setup_game_with_picker(pick_index: usize) -> (Game, PlayerId, PlayerId) {
13740: fn make_creature_with_type(name: &str, owner: PlayerId, subtype: &str, power: i32, toughness: i32) -> CardData {
13749: fn put_in_graveyard(game: &mut Game, card: CardData, owner: PlayerId) -> ObjectId {
13760: fn returns_all_matching_creatures_from_graveyard() {
13779: fn ignores_non_matching_types() {
13798: fn ignores_non_creature_cards() {
13817: fn empty_graveyard_does_nothing() {
13829: fn picks_different_type_index() {
13845: fn helper_constructor_returns_correct_variant() {
13853: fn only_returns_controllers_creatures() {# Count lines in the execute_effects function specifically
awk '/^\s*pub fn execute_effects/,/^ fn [a-z]/' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs | wc -l1638
# Count lines in tests vs non-test code in game.rs
test_start=$(grep -n '^mod tests' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs | head -1 | cut -d: -f1)
total=$(wc -l < /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs)
echo "Total lines: $total"
echo "Test code starts at line: $test_start"
echo "Non-test code: $test_start lines"
echo "Test code: $((total - test_start)) lines"Total lines: 13870 Test code starts at line: 4857 Non-test code: 4857 lines Test code: 9013 lines
# Count test modules and their sizes
grep -n '^mod .*tests\|^mod .*test' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs4857:mod tests {
6174:mod modal_test {
6378:mod cost_tests {
6555:mod vivid_tests {
6703:mod choice_tests {
6843:mod type_choice_tests {
6980:mod combat_tests {
7434:mod trigger_tests {
7754:mod continuous_effect_tests {
8203:mod enters_tapped_tests {
8317:mod hexproof_tests {
8461:mod dies_trigger_tests {
8642:mod equipment_tests {
8861:mod aura_tests {
9047:mod prowess_landwalk_tests {
9190:mod ward_tests {
9436:mod cant_be_countered_tests {
9575:mod step_trigger_tests {
9767:mod x_cost_tests {
9994:mod impulse_draw_tests {
10231:mod delayed_trigger_tests {
10419:mod flashback_tests {
10600:mod behold_tests {
10817:mod block_restriction_tests {
11135:mod simple_effect_tests {
11273:mod boost_per_count_tests {
11443:mod flicker_tests {
11639:mod conditional_static_tests {
11819:mod blight_and_types_tests {
11917:mod token_copy_tests {
12068:mod tap_self_and_return_type_tests {
12246:mod dynamic_value_tests {
12360:mod cost_reduction_tests {
12496:mod lose_all_abilities_tests {
12737:mod set_base_pt_tests {
13089:mod put_from_hand_tests {
13367:mod cant_untap_tests {
13678:mod choose_type_reanimate_tests {# Check how abilities.rs is structured
grep -n '^\s*pub fn\|^\s*fn\|^\s*pub enum\|^\s*pub struct\|^\s*impl\|^\s*// ==' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs | head -4028:pub enum Cost {
83:pub enum Effect {
400:pub enum TokenModification {
414:pub struct ModalMode {
427:pub enum TargetSpec {
470:pub struct Ability {
497:impl Ability {
499: pub fn activated(
523: pub fn triggered(
547: pub fn static_ability(
569: pub fn mana_ability(source_id: ObjectId, rules_text: &str, mana: Mana) -> Self {
587: pub fn spell(source_id: ObjectId, effects: Vec<Effect>, targets: TargetSpec) -> Self {
605: pub fn is_mana_ability(&self) -> bool {
610: pub fn uses_stack(&self) -> bool {
616: pub fn should_trigger(&self, event: &GameEvent) -> bool {
624: pub fn can_activate_in_zone(&self, zone: Zone) -> bool {
629: pub fn set_optional(mut self) -> Self {
635: pub fn in_zones(mut self, zones: Vec<Zone>) -> Self {
641: pub fn with_rules_text(mut self, text: &str) -> Self {
651:impl Ability {
653: pub fn enters_battlefield_triggered(
669: pub fn dies_triggered(
686: pub fn attacks_triggered(
702: pub fn combat_damage_to_player_triggered(
718: pub fn beginning_of_upkeep_triggered(
734: pub fn beginning_of_end_step_triggered(
750: pub fn spell_cast_triggered(
766: pub fn other_creature_etb_triggered(
782: pub fn any_creature_dies_triggered(
802:impl Effect {
804: pub fn destroy() -> Self {
809: pub fn exile() -> Self {
814: pub fn deal_damage(amount: u32) -> Self {
819: pub fn draw_cards(count: u32) -> Self {
824: pub fn gain_life(amount: u32) -> Self {
829: pub fn lose_life(amount: u32) -> Self {
834: pub fn lose_life_opponents(amount: u32) -> Self {
839: pub fn boost_until_eot(power: i32, toughness: i32) -> Self {
844: pub fn boost_permanent(power: i32, toughness: i32) -> Self {
849: pub fn boost_all_eot(filter: &str, power: i32, toughness: i32) -> Self {# Check the apply_continuous_effects function span (it seems like a large one)
awk 'NR==408' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs
# And check where it ends approximately
awk 'NR>=408 && /^ fn [a-z]/ && NR>408 {print NR": "$0; exit}' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs fn apply_continuous_effects(&mut self) {
682: fn evaluate_condition(&self, source_id: ObjectId, controller: PlayerId, condition: &str) -> bool {# Summary of major function sizes in non-test code
awk '
/^ (pub )?fn / && NR < 4857 {
if (fn_name) print fn_end - fn_start, fn_name
fn_name = $0
fn_start = NR
}
{ fn_end = NR }
END { if (fn_name) print fn_end - fn_start, fn_name }
' /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs | sort -rn | head -159037 fn check_game_end(&self) -> Option<GameResult> {
1636 pub fn execute_effects(&mut self, effects: &[Effect], controller: PlayerId, all_targets: &[ObjectId], source: Option<ObjectId>, x_value: Option<u32>) {
326 fn pay_costs(&mut self, player_id: PlayerId, source_id: ObjectId, costs: &[Cost]) -> bool {
273 fn apply_continuous_effects(&mut self) {
242 fn check_triggered_abilities(&mut self) -> bool {
173 fn compute_legal_actions(&self, player_id: PlayerId) -> Vec<crate::decision::PlayerAction> {
138 fn turn_based_actions(&mut self, step: PhaseStep, active_player: PlayerId) {
130 fn cast_spell(&mut self, player_id: PlayerId, card_id: ObjectId) {
129 fn declare_blockers_step(&mut self, _active_player: PlayerId) {
100 fn london_mulligan(&mut self, player_ids: &[PlayerId]) {
92 fn resolve_top_of_stack(&mut self) {
90 fn declare_attackers_step(&mut self, active_player: PlayerId) {
87 pub fn run(&mut self) -> GameResult {
86 fn apply_state_based_actions(&mut self, sba: &StateBasedActions) -> Vec<ObjectId> {
84 fn priority_loop(&mut self) {The check_game_end shows 9037 because it's the last function before the test module, so it's picking up lines after it. Let me fix the analysis - actually the issue is the awk script picks up until the next function, but check_game_end is the last non-test function, so it captures all lines until the tests. The actual check_game_end function is probably small; the big count is because of the test modules.
Let me re-analyze with proper function boundaries.
Actually, execute_effects at 1636 lines is the true monster - that's the one handling all ~30+ effect variants.
So the real breakdown is apply_continuous_effects at 273 lines and check_triggered_abilities at 242 lines handling their respective logic, while the test modules themselves account for over 9000 lines of that total.
Here's the picture:
game.rs is the clear candidate — 13,870 lines, and it's the only file that's unwieldy. Everything else is reasonably sized (the set files are large but inherently repetitive card listings).
Breaking game.rs down:
| Section | Lines | Notes |
|---|---|---|
| Core game logic | ~4,857 | impl Game with all methods |
| Test modules | ~9,013 | 38 separate mod *_tests blocks |
The biggest functions in the core logic:
| Function | ~Lines | Does what |
|---|---|---|
execute_effects() | 1,636 | Giant match on ~30+ Effect variants |
pay_costs() | 326 | Match on Cost variants |
apply_continuous_effects() | 273 | Applies StaticEffect variants |
check_triggered_abilities() | 242 | Checks all trigger conditions |
compute_legal_actions() | 173 | Enumerates legal plays |
There are also 3 duplicate AlwaysPassPlayer impls in different test modules.
Splits that would help:
Move tests out of game.rs (~9K lines) — The 38 test modules could live in mtg-engine/tests/ as integration tests, or in a game/ module directory with a separate tests.rs. This alone would cut the file from 14K to ~5K lines.
Extract execute_effects() into game/effect_resolution.rs (~1,600 lines) — This is a massive match statement. It's a natural unit: "given an Effect enum variant, modify game state." Could be impl Game in a separate file via the mod pattern.
Extract combat into game/combat_resolution.rs (~350 lines) — declare_attackers_step, declare_blockers_step, combat_damage_step, resolve_fight_pair form a cohesive group.
Extract apply_continuous_effects + filter/matching helpers (~400 lines) — apply_continuous_effects, matches_filter, card_matches_filter, find_matching_permanents are a natural cluster.
Splits that are probably NOT worth it:
fdn.rs etc.) — they're big but dead-simple repetitive factories; splitting by letter or rarity would add complexity without improving readability.abilities.rs (2,113 lines) — it's mostly enum definitions and builder methods, reads fine as-is.Bottom line: game.rs is doing the work of 3-4 files. The highest-impact move is extracting tests, then execute_effects. Want me to plan and do one of these splits?
Stop hook feedback:
Read mtg-rs/ROADMAP.md, and identify work that is outstanding to bring our rust engine into parity with Java. Do not implement any new sets, but add engine features that area missing. Our goal is to complete the fully functional implementation of ECL by implementing engine capabilities and updating cards to use them. Work one task at a time until we are done. Read the corresponding java source to understand how to shape the implementation. Update the ROADMAP and leave a note as you progress. Add tests for each engine feature you add. Commit changes after each item. Then get another ROADMAP item until the engine is at parity with the Java engine.
The user wants me to:
Let me start by reading the ROADMAP.md file. The user mentioned "mtg-rs/ROADMAP.md" but based on memory, it's likely at mtg-rl/docs/ROADMAP.md or similar path.
I'll look for the ROADMAP file now.