WOS Sim
← Back to Runs

Compare Runs

A (baseline)
0fcffb9d-974bef5a41327/06/2026, 03:34:30
B (current)
4146c453-d177f8f128629/06/2026, 21:22:06
Avg Error A0.20%
Avg Error B0.19%
Δ Avg Error-0.01%
Improved2
Regressed0
Added0
Retired0

Testcase Delta

201 / 201
FileCase#AdjA%B%Δ%Status
testcases/emulator_verified/wos451_gordon_s21_lancer_damage_up_bucket.jsonwos451_gordon_s21_lancer_damage_up_bucket0-0.025%-0.69%0.00%-0.69%improved
testcases/emulator_verified/wos451_gordon_s11_lancer_damage_up_bucket.jsonwos451_gordon_s11_lancer_damage_up_bucket0-0.05%-0.66%0.00%-0.66%improved
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_1010.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_1120.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_1230.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_1340.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_1450.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_156+0.013%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_167+0.008%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_178-0.003%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_189+0.017%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_19100.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2011-0.006%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2112+0.013%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_22130.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_23140.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2415-0.008%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_25160.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2617-0.002%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_900.00%0.00%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_10+0.05%-0.32%-0.32%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_21+0.05%-0.06%-0.06%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_32+0.05%-0.38%-0.38%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_43+0.05%-0.45%-0.45%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_54+0.043%0.00%0.00%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_65+0.05%-0.07%-0.07%0.00%unchanged
testcases/3-testcases_mixed-heroes-not-verified.jsondaut_viper_10+0.05%0.19%0.19%0.00%unchanged
testcases/3-testcases_mixed-heroes-not-verified.jsondaut_viper_210.00%0.00%0.00%unchanged
testcases/3-testcases_mixed-heroes-not-verified.jsondaut_viper_32-0.42%-0.42%0.00%unchanged
testcases/3-testcases_mixed-heroes-not-verified.jsondaut_viper_430.02%0.02%0.00%unchanged
testcases/3-testcases_mixed-heroes-not-verified.jsondaut_viper_540.64%0.64%0.00%unchanged
testcases/3-testcases_mixed-heroes-not-verified.jsondaut_viper_750.16%0.16%0.00%unchanged
testcases/4-testcases_no-heroes_infantry_fc5.jsonNitro_Beast_30_10-0.22%-0.22%0.00%unchanged
testcases/emulator_verified/ahmose_solo_nc.jsonahmose_solo0-0.05%0.06%0.06%0.00%unchanged
testcases/emulator_verified/alonso_attacker_600_all.jsonalonso_attacker_600_all0-0.14%-0.14%0.00%unchanged
testcases/emulator_verified/alonso_attacker_900_inf_lancer.jsonalonso_attacker_900_inf_lancer00.04%0.04%0.00%unchanged
testcases/emulator_verified/alonso_attacker_900_inf_lancer.jsonalonso_attacker_900_inf_lancer10.02%0.02%0.00%unchanged
testcases/emulator_verified/alonso_minxxx_defender.jsonalonso_minxxx_defender00.14%0.14%0.00%unchanged
testcases/emulator_verified/alonso_norah_combo.jsonalonso_norah_combo0-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/alonso_norah_combo.jsonalonso_norah_combo1-0.26%-0.26%0.00%unchanged
testcases/emulator_verified/alonso_norah_combo.jsonalonso_norah_combo2-0.19%-0.19%0.00%unchanged
testcases/emulator_verified/alonso_solo.jsonalonso_solo02.97%2.97%0.00%unchanged
testcases/emulator_verified/alonso_solo_v2.jsonalonso_solo_v201.58%1.58%0.00%unchanged
testcases/emulator_verified/alonso_solo_v2.jsonalonso_solo_v21-1.59%-1.59%0.00%unchanged
testcases/emulator_verified/alonso_vs_alonso.jsonalonso_vs_alonso0-0.17%-0.17%0.00%unchanged
testcases/emulator_verified/alonso_vs_alonso_inf_lancer.jsonalonso_vs_alonso_inf_lancer0-0.04%-0.04%0.00%unchanged
testcases/emulator_verified/alonso_wip_attacker.jsonalonso_wip_attacker0-0.03%-0.03%0.00%unchanged
testcases/emulator_verified/ambusher_no_marksmen.jsonambusher_no_marksmen01.45%1.45%0.00%unchanged
testcases/emulator_verified/attdef_jasser_patrick_minxxx_attacks_nc.jsonattdef_jasser_patrick_minxxx_attacks0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/attdef_jasser_patrick_nc.jsonattdef_jasser_patrick00.00%0.00%0.00%unchanged
testcases/emulator_verified/attdef_jasser_patrick_swap_nc.jsonattdef_jasser_patrick_swap00.00%0.00%0.00%unchanged
testcases/emulator_verified/attdef_jasser_patrick_wip_attacks_nc.jsonattdef_jasser_patrick_wip_attacks0-0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/attdef_jasser_patrick_wip_attacks_nc.jsonattdef_jasser_patrick_wip_attacks1-0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/attdef_test_minxxx_attacks_nc.jsonattdef_test_minxxx_attacks_nc0-0.05%0.08%0.08%0.00%unchanged
testcases/emulator_verified/attdef_test_wip_attacks_nc.jsonattdef_test_wip_attacks_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/bahiti_norah_combo_nc.jsonbahiti_norah_combo_nc0-3.15%-3.15%0.00%unchanged
testcases/emulator_verified/bahiti_solo.jsonbahiti_solo00.32%0.32%0.00%unchanged
testcases/emulator_verified/bahiti_solo_nc.jsonbahiti_solo_nc00.04%0.04%0.00%unchanged
testcases/emulator_verified/determinism_test_normal_nc.jsondeterminism_test_normal00.00%0.00%0.00%unchanged
testcases/emulator_verified/determinism_test_swapped_nc.jsondeterminism_test_swapped00.00%0.00%0.00%unchanged
testcases/emulator_verified/flint_reina_zinman_combo.jsonflint_reina_zinman_combo0-0.15%-0.15%0.00%unchanged
testcases/emulator_verified/flint_solo_nc.jsonflint_solo0+0.05%-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/flint_zinman_combo_nc.jsonflint_zinman_combo_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/greg_mia_combo.jsongreg_mia_combo0-1.97%-1.97%0.00%unchanged
testcases/emulator_verified/greg_mia_combo.jsongreg_mia_combo1-0.05%1.06%1.06%0.00%unchanged
testcases/emulator_verified/greg_mia_defender_current.jsongreg_mia_defender_current0-0.09%-0.09%0.00%unchanged
testcases/emulator_verified/greg_mia_nohero_control_current.jsongreg_mia_nohero_control_current00.00%0.00%0.00%unchanged
testcases/emulator_verified/greg_mia_nohero_control_current.jsongreg_mia_nohero_control_current1+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/greg_only_defender_current.jsongreg_only_defender_current00.00%0.00%0.00%unchanged
testcases/emulator_verified/greg_only_defender_current.jsongreg_only_defender_current10.01%0.01%0.00%unchanged
testcases/emulator_verified/greg_only_defender_current.jsongreg_only_defender_current2-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/greg_solo.jsongreg_solo00.16%0.16%0.00%unchanged
testcases/emulator_verified/gwen_norah_combo_nc.jsongwen_norah_combo_nc0-0.60%-0.60%0.00%unchanged
testcases/emulator_verified/gwen_solo_nc.jsongwen_solo0+0.05%-0.79%-0.79%0.00%unchanged
testcases/emulator_verified/hector_patrick_combo.jsonhector_patrick_combo0-0.05%-0.05%0.00%unchanged
testcases/emulator_verified/hector_renee_wayne.jsonhector_renee_wayne0-0.45%-0.45%0.00%unchanged
testcases/emulator_verified/hector_solo.jsonhector_solo00.12%0.12%0.00%unchanged
testcases/emulator_verified/jasser_solo_nc.jsonjasser_solo0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/jessie_solo_nc.jsonjessie_solo0-0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/ling_xue_solo_nc.jsonling_xue_solo0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/ling_xue_solo_nc.jsonling_xue_solo1+0.05%-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/logan_bahiti_combo_nc.jsonlogan_bahiti_combo_nc00.55%0.55%0.00%unchanged
testcases/emulator_verified/logan_gwen_combo_nc.jsonlogan_gwen_combo_nc0+0.05%-0.25%-0.25%0.00%unchanged
testcases/emulator_verified/logan_lynn_combo.jsonlogan_lynn_combo00.08%0.08%0.00%unchanged
testcases/emulator_verified/logan_lynn_combo.jsonlogan_lynn_combo10.03%0.03%0.00%unchanged
testcases/emulator_verified/logan_lynn_combo.jsonlogan_lynn_combo20.00%0.00%0.00%unchanged
testcases/emulator_verified/logan_reina_bahiti_combo_nc.jsonlogan_reina_bahiti_combo_nc0-0.60%-0.60%0.00%unchanged
testcases/emulator_verified/logan_solo_minxxx_nc.jsonlogan_solo_minxxx00.00%0.00%0.00%unchanged
testcases/emulator_verified/logan_solo_v2_nc.jsonlogan_solo_v200.00%0.00%0.00%unchanged
testcases/emulator_verified/lumak_bokan_solo_nc.jsonlumak_bokan_solo0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/lumak_bokan_solo_nc.jsonlumak_bokan_solo1+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/lynn_solo.jsonlynn_solo0-0.08%-0.08%0.00%unchanged
testcases/emulator_verified/mia_only_defender_current.jsonmia_only_defender_current0-0.07%-0.07%0.00%unchanged
testcases/emulator_verified/mia_only_defender_current.jsonmia_only_defender_current1-0.21%-0.21%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo0-0.27%-0.27%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo10.22%0.22%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo2-0.32%-0.32%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo30.04%0.04%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo40.21%0.21%0.00%unchanged
testcases/emulator_verified/molly_lynn_combo.jsonmolly_lynn_combo00.00%0.00%0.00%unchanged
testcases/emulator_verified/molly_solo.jsonmolly_solo0-0.07%-0.07%0.00%unchanged
testcases/emulator_verified/natalia_solo.jsonnatalia_solo0+0.05%-1.06%-1.06%0.00%unchanged
testcases/emulator_verified/norah_greg_combo.jsonnorah_greg_combo0-0.04%-0.04%0.00%unchanged
testcases/emulator_verified/norah_hector_zinman_combo.jsonnorah_hector_zinman_combo00.07%0.07%0.00%unchanged
testcases/emulator_verified/norah_s2_inf_only_A.jsonnorah_s2_inf_only_A0+0.05%-0.35%-0.35%0.00%unchanged
testcases/emulator_verified/norah_s2_inf_only_B_nc.jsonnorah_s2_inf_only_B0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/norah_s2_splash_A.jsonnorah_s2_splash_A00.23%0.23%0.00%unchanged
testcases/emulator_verified/norah_s2_splash_B_nc.jsonnorah_s2_splash_B00.00%0.00%0.00%unchanged
testcases/emulator_verified/norah_s2_splash_C_nc.jsonnorah_s2_splash_C00.00%0.00%0.00%unchanged
testcases/emulator_verified/norah_s2_splash_D.jsonnorah_s2_splash_D00.12%0.12%0.00%unchanged
testcases/emulator_verified/norah_solo.jsonnorah_solo0-0.28%-0.28%0.00%unchanged
testcases/emulator_verified/norah_zinman_combo_nc.jsonnorah_zinman_combo_nc00.26%0.26%0.00%unchanged
testcases/emulator_verified/patrick_solo_nc.jsonpatrick_solo0+0.001%-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/philly_bahiti_combo.jsonphilly_bahiti_combo0-0.14%-0.14%0.00%unchanged
testcases/emulator_verified/philly_bahiti_combo.jsonphilly_bahiti_combo10.22%0.22%0.00%unchanged
testcases/emulator_verified/philly_solo.jsonphilly_solo00.20%0.20%0.00%unchanged
testcases/emulator_verified/philly_solo.jsonphilly_solo10.02%0.02%0.00%unchanged
testcases/emulator_verified/reina_attacker_wip.jsonreina_attacker_wip00.01%0.01%0.00%unchanged
testcases/emulator_verified/reina_bahiti_combo_nc.jsonreina_bahiti_combo_nc01.05%1.05%0.00%unchanged
testcases/emulator_verified/reina_logan_combo_simulator.jsonreina_logan_combo_simulator00.14%0.14%0.00%unchanged
testcases/emulator_verified/reina_solo_wip.jsonreina_solo_wip0-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/renee_solo_nc.jsonrenee_solo_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/seo_yoon_solo_nc.jsonseo_yoon_solo0-0.05%0.10%0.10%0.00%unchanged
testcases/emulator_verified/sergey_norah_zinman_combo_nc.jsonsergey_norah_zinman_combo_nc0-1.58%-1.58%0.00%unchanged
testcases/emulator_verified/sergey_reina_combo_nc.jsonsergey_reina_combo_nc0-0.35%-0.35%0.00%unchanged
testcases/emulator_verified/sergey_solo_nc.jsonsergey_solo0-0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/simple_001_nc.jsonsimple_00100.00%0.00%0.00%unchanged
testcases/emulator_verified/troop_check_nc.jsontroop_check00.00%0.00%0.00%unchanged
testcases/emulator_verified/type_triangle_A_nc.jsontype_triangle_A_nc0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/type_triangle_B_nc.jsontype_triangle_B_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wayne_mixed_solo.jsonwayne_mixed_solo0-0.23%-0.23%0.00%unchanged
testcases/emulator_verified/wayne_mixed_solo.jsonwayne_mixed_solo1-2.01%-2.01%0.00%unchanged
testcases/emulator_verified/wayne_reina_combo.jsonwayne_reina_combo0-0.37%-0.37%0.00%unchanged
testcases/emulator_verified/wayne_reina_wuming_combo.jsonwayne_reina_wuming_combo00.18%0.18%0.00%unchanged
testcases/emulator_verified/wayne_s1_solo.jsonwayne_s1_solo00.36%0.36%0.00%unchanged
testcases/emulator_verified/wayne_s2_solo.jsonwayne_s2_solo00.43%0.43%0.00%unchanged
testcases/emulator_verified/wos425_wip_nohero_control_nc.jsonwos425_wip_nohero_control_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos425_wip_patrick_health_nc.jsonwos425_wip_patrick_health_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos425_wip_philly_defense.jsonwos425_wip_philly_defense0-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/wos425_wip_sergey_damage_taken_nc.jsonwos425_wip_sergey_damage_taken_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos425_wip_sergey_patrick_combo_nc.jsonwos425_wip_sergey_patrick_combo_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos425_wip_sergey_philly_combo.jsonwos425_wip_sergey_philly_combo0-0.04%-0.04%0.00%unchanged
testcases/emulator_verified/wos427_wip_sergey_philly_840.jsonwos427_wip_sergey_philly_8400-0.85%-0.85%0.00%unchanged
testcases/emulator_verified/wos444_bradley_s3_all_troops_damage_control_nc.jsonwos444_bradley_s3_all_troops_damage_control_nc0+0.05%-0.82%-0.82%0.00%unchanged
testcases/emulator_verified/wos444_bradley_s3_all_troops_damage_control_nc.jsonwos444_bradley_s3_all_troops_damage_control_nc10.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_bradley_s3_all_troops_damage_control_nc.jsonwos444_bradley_s3_all_troops_damage_control_nc20.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_bradley_s3_all_troops_damage_hero_nc.jsonwos444_bradley_s3_all_troops_damage_hero_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_edith_s11_mark_damage_taken_control_nc.jsonwos444_edith_s11_mark_damage_taken_control_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_edith_s11_mark_damage_taken_control_nc.jsonwos444_edith_s11_mark_damage_taken_control_nc10.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_edith_s11_mark_damage_taken_hero_nc.jsonwos444_edith_s11_mark_damage_taken_hero_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_edith_s12_lancer_damage_dealt_control_nc.jsonwos444_edith_s12_lancer_damage_dealt_control_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_edith_s12_lancer_damage_dealt_hero_nc.jsonwos444_edith_s12_lancer_damage_dealt_hero_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_gordon_s22_all_enemy_damage_dealt_control_nc.jsonwos444_gordon_s22_all_enemy_damage_dealt_control_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_gordon_s22_all_enemy_damage_dealt_control_nc.jsonwos444_gordon_s22_all_enemy_damage_dealt_control_nc1+0.05%-0.41%-0.41%0.00%unchanged
testcases/emulator_verified/wos444_gordon_s22_all_enemy_damage_dealt_control_nc.jsonwos444_gordon_s22_all_enemy_damage_dealt_control_nc20.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_gordon_s22_all_enemy_damage_dealt_hero_nc.jsonwos444_gordon_s22_all_enemy_damage_dealt_hero_nc00.11%0.11%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s21_lancer_damage_up_bucket.jsonwos451_bradley_s21_lancer_damage_up_bucket00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s21_lancer_damage_up_control.jsonwos451_bradley_s21_lancer_damage_up_control0-0.025%0.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s22_infantry_damage_up_bucket.jsonwos451_bradley_s22_infantry_damage_up_bucket0-0.05%0.03%0.03%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s22_infantry_damage_up_control.jsonwos451_bradley_s22_infantry_damage_up_control00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s3_all_damage_up_bucket.jsonwos451_bradley_s3_all_damage_up_bucket00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s3_all_damage_up_control.jsonwos451_bradley_s3_all_damage_up_control00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_gordon_s11_lancer_damage_up_control.jsonwos451_gordon_s11_lancer_damage_up_control0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_gordon_s21_lancer_damage_up_control.jsonwos451_gordon_s21_lancer_damage_up_control00.00%0.00%0.00%unchanged
testcases/emulator_verified/wu_ming_solo_nc.jsonwu_ming_solo00.00%0.00%0.00%unchanged
testcases/emulator_verified/zinman_solo_nc.jsonzinman_solo0+0.05%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Flint_tc.jsondaut_viper_10+0.05%-0.05%-0.05%0.00%unchanged
testcases/heroes_unittests/Flint_tc.jsondaut_viper_21-0.05%0.27%0.27%0.00%unchanged
testcases/heroes_unittests/Hector_tc.jsondaut_viper_100.00%0.00%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_10+0.05%-0.06%-0.06%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_21+0.05%-0.07%-0.07%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_32-0.025%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_43-0.017%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_540.00%0.00%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_65+0.05%-0.06%-0.06%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_76+0.05%-0.05%-0.05%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_870.00%0.00%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_100.17%0.17%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_21+0.05%-0.77%-0.77%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_32-0.10%-0.10%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_43-0.13%-0.13%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_54-0.11%-0.11%0.00%unchanged
testcases/heroes_unittests/Molly_tc.jsondaut_viper_10+0.05%-0.06%-0.06%0.00%unchanged
testcases/heroes_unittests/Molly_tc.jsondaut_viper_21+0.05%-0.07%-0.07%0.00%unchanged
testcases/heroes_unittests/Molly_tc.jsondaut_viper_32-0.025%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Molly_tc.jsondaut_viper_430.33%0.33%0.00%unchanged
testcases/heroes_unittests/Molly_tc.jsondaut_viper_54-0.02%-0.02%0.00%unchanged
testcases/heroes_unittests/Reina_tc.jsondaut_viper_10+0.05%-0.18%-0.18%0.00%unchanged
testcases/heroes_unittests/Reina_tc.jsondaut_viper_210.00%0.00%0.00%unchanged
testcases/heroes_unittests/Reina_tc.jsondaut_viper_42-0.46%-0.46%0.00%unchanged
testcases/heroes_unittests/Reina_tc.jsondaut_viper_530.01%0.01%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_10+0.05%-0.06%-0.06%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_32-0.025%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_43+0.05%-0.04%-0.04%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_54+0.05%-0.06%-0.06%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_61+0.05%-0.07%-0.07%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_65-0.006%0.04%0.04%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_76-0.004%0.04%0.04%0.00%unchanged
testcases/heroes_unittests/Zinman_tc.jsondaut_viper_10-0.05%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Zinman_tc.jsondaut_viper_21-0.025%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Zinman_tc.jsondaut_viper_320.00%0.00%0.00%unchanged

Code / Config Changes

Commits (bef5a4137f8f1286)

7f8f1286Show full dashboard in prodpiddlyminx29/06/2026, 08:34:38

Code Changes (Run A → Run B)

Index: simulator/config/hero_definitions/Gordon.json
===================================================================
--- simulator/config/hero_definitions/Gordon.json prev run
+++ simulator/config/hero_definitions/Gordon.json this run
@@ -11,9 +11,9 @@
"source": "lancer"
},
"effects": {
"VenomInfusion/1": {
- "type": "active.hero.attack.up",
+ "type": "type.normal.damage.up",
"value": [
20,
40,
60,
Show raw per-run patches
Run A dirty state patch
diff --git a/simulator/config/hero_definitions/Gwen.json b/simulator/config/hero_definitions/Gwen.json
--- a/simulator/config/hero_definitions/Gwen.json
+++ b/simulator/config/hero_definitions/Gwen.json
@@ -6,8 +6,8 @@
"EagleVision": {
"description": "Increasing target's damage taken by X%",
"trigger": {
- "type": "attack",
- "every": 2
+ "type": "turn",
+ "source": "self.any"
},
"effects": {
"EagleVision/1": {
@@ -20,7 +20,8 @@
25
],
"units": {
- "applies_to": "trigger.target"
+ "applies_to": "trigger.target",
+ "applies_vs": "trigger.source"
},
"duration": {
"type": "attack",
diff --git a/simulator/src/damage.ts b/simulator/src/damage.ts
--- a/simulator/src/damage.ts
+++ b/simulator/src/damage.ts
@@ -62,9 +62,6 @@
export interface DamageResult {
kills: number;
consumedEffectIds: string[];
- consumedEffectUseKey?: string;
- consumedEffectUseId?: string;
- consumedEffectUseIds?: string[];
appliedEffectIds?: string[];
appliedEffects?: DamageEquationTrace["appliedEffects"];
trace?: DamageEquationTrace;
@@ -187,15 +184,11 @@
}
: undefined;
- const returnedConsumedEffectIds =
- consumedEffectIds.size === 0 ? job.consumedEffectIds ?? EMPTY_CONSUMED_EFFECT_IDS : [...consumedEffectIds, ...(job.consumedEffectIds ?? [])];
+ const returnedConsumedEffectIds = consumedEffectIds.size === 0 ? EMPTY_CONSUMED_EFFECT_IDS : [...consumedEffectIds];
return {
kills,
consumedEffectIds: returnedConsumedEffectIds,
- consumedEffectUseKey: job.consumedEffectUseKey,
- consumedEffectUseId: job.consumedEffectUseId,
- consumedEffectUseIds: job.consumedEffectUseIds,
appliedEffectIds: traceEnabled ? appliedEffects.map((effect) => effect.effectId) : undefined,
appliedEffects: traceEnabled ? appliedEffects : undefined,
trace
diff --git a/simulator/src/effectIndex.ts b/simulator/src/effectIndex.ts
--- a/simulator/src/effectIndex.ts
+++ b/simulator/src/effectIndex.ts
@@ -58,9 +58,9 @@
definition.role === "attacker"
? damageJobShapeSlot(jobKind, effect.appliesTo.side, appliesToUnit, effect.appliesVs.side, appliesVsUnit)
: damageJobShapeSlot(jobKind, effect.appliesVs.side, appliesVsUnit, effect.appliesTo.side, appliesToUnit);
- const effects = index.damageByJobShape[slot];
+ const arr = index.damageByJobShape[slot];
const candidate = { effect, bucket: definition.path };
- if (effects) effects.push(candidate);
+ if (arr) arr.push(candidate);
else index.damageByJobShape[slot] = [candidate];
}
}
@@ -73,20 +73,24 @@
compactEffects(index.extraAttacks, isActive);
compactEffects(index.battleOrder, isActive);
for (let slot = 0; slot < index.damageByJobShape.length; slot += 1) {
- const candidates = index.damageByJobShape[slot];
- if (!candidates) continue;
- compactCandidates(candidates, isActive);
- if (candidates.length === 0) index.damageByJobShape[slot] = undefined;
+ const arr = index.damageByJobShape[slot];
+ if (!arr) continue;
+ compactCandidates(arr, isActive);
+ if (arr.length === 0) index.damageByJobShape[slot] = undefined;
}
}
export function removeStaticProfileBucketEffects(index: EffectIndex): void {
compactEffects(index.all, (effect) => !isStaticProfileEffect(effect));
for (let slot = 0; slot < index.damageByJobShape.length; slot += 1) {
- const candidates = index.damageByJobShape[slot];
- if (!candidates) continue;
- compactCandidates(candidates, (_effect, candidate) => !isStaticProfileBucket(candidate.bucket));
- if (candidates.length === 0) index.damageByJobShape[slot] = undefined;
+ const arr = index.damageByJobShape[slot];
+ if (!arr) continue;
+ let write = 0;
+ for (let read = 0; read < arr.length; read += 1) {
+ if (!isStaticProfileBucket(arr[read].bucket)) { arr[write] = arr[read]; write += 1; }
+ }
+ arr.length = write;
+ if (arr.length === 0) index.damageByJobShape[slot] = undefined;
}
}
@@ -104,20 +108,19 @@
return (((kindIndex(jobKind) * 2 + sideIndex(attackerSide)) * 3 + unitIndex(attackerUnit)) * 2 + sideIndex(defenderSide)) * 3 + unitIndex(defenderUnit);
}
-function kindIndex(kind: DamageKind): number {
- return kind === "normal" ? 0 : 1;
-}
-
-function sideIndex(side: SideId): number {
- return side === "attacker" ? 0 : 1;
-}
-
+function kindIndex(kind: DamageKind): number { return kind === "normal" ? 0 : 1; }
+function sideIndex(side: SideId): number { return side === "attacker" ? 0 : 1; }
function unitIndex(unit: UnitType): number {
if (unit === "infantry") return 0;
if (unit === "lancer") return 1;
return 2;
}
+function isStaticProfileEffect(effect: ActiveEffect): boolean {
+ const definition = bucketDefinition(effect.intent.type);
+ return isStaticProfileBucket(effect.intent.type) || (definition !== undefined && isStaticProfileBucket(definition.path));
+}
+
function compactEffects(effects: ActiveEffect[], isActive: (effect: ActiveEffect) => boolean): void {
let write = 0;
for (let read = 0; read < effects.length; read += 1) {
@@ -129,16 +132,11 @@
effects.length = write;
}
-function isStaticProfileEffect(effect: ActiveEffect): boolean {
- const definition = bucketDefinition(effect.intent.type);
- return isStaticProfileBucket(effect.intent.type) || (definition !== undefined && isStaticProfileBucket(definition.path));
-}
-
-function compactCandidates(candidates: IndexedBucketEffect[], isActive: (effect: ActiveEffect, candidate: IndexedBucketEffect) => boolean): void {
+function compactCandidates(candidates: IndexedBucketEffect[], isActive: (effect: ActiveEffect) => boolean): void {
let write = 0;
for (let read = 0; read < candidates.length; read += 1) {
const candidate = candidates[read];
- if (!isActive(candidate.effect, candidate)) continue;
+ if (!isActive(candidate.effect)) continue;
candidates[write] = candidate;
write += 1;
}
diff --git a/simulator/src/recorder.ts b/simulator/src/recorder.ts
--- a/simulator/src/recorder.ts
+++ b/simulator/src/recorder.ts
@@ -106,9 +106,6 @@
appliedEffectIds: result.appliedEffectIds ?? [],
appliedEffects: result.appliedEffects ?? [],
consumedEffectIds: result.consumedEffectIds,
- consumedEffectUseKey: result.consumedEffectUseKey,
- consumedEffectUseId: result.consumedEffectUseId,
- consumedEffectUseIds: result.consumedEffectUseIds,
trace: result.trace
});
}
diff --git a/simulator/src/simulator.test.ts b/simulator/src/simulator.test.ts
--- a/simulator/src/simulator.test.ts
+++ b/simulator/src/simulator.test.ts
@@ -647,10 +647,13 @@
{ mode: "trace" }
);
+ const roundTwoNormalOutcome = result.attacks.find((attack) => attack.kind === "normal" && attack.jobId.startsWith("r2:attacker:infantry"));
const roundTwoSkillOutcome = result.attacks.find((attack) => attack.kind === "skill" && attack.jobId.startsWith("r2:attacker:infantry"));
+ assert.notEqual(roundTwoNormalOutcome, undefined);
+ assert.equal(roundTwoNormalOutcome!.consumedEffectIds.length, 2);
assert.notEqual(roundTwoSkillOutcome, undefined);
- assert.equal(roundTwoSkillOutcome!.consumedEffectIds.length, 2);
+ assert.equal(roundTwoSkillOutcome!.consumedEffectIds.length, 0);
});
test("requires_effect is ignored by native simulator effect activation", () => {
diff --git a/simulator/src/simulator.ts b/simulator/src/simulator.ts
--- a/simulator/src/simulator.ts
+++ b/simulator/src/simulator.ts
@@ -35,7 +35,7 @@
type Rng
} from "./effects";
import { classifyEffectForJob } from "./classifier";
-import { createEffectIndex, indexEffect, pruneEffectIndex, removeStaticProfileBucketEffects, type EffectIndex } from "./effectIndex";
+import { bucketCandidatesForJob, createEffectIndex, indexEffect, pruneEffectIndex, removeStaticProfileBucketEffects, type EffectIndex } from "./effectIndex";
import { normalizeUnitType } from "./normalize";
import { emptyTroops, resolveFighter } from "./resolve";
import { buildStaticDamageProfile, type StaticDamageProfile } from "./staticDamageProfile";
@@ -49,6 +49,7 @@
interface Runtime {
activeEffects: ActiveEffect[];
effectIndex: EffectIndex;
+ effectById: Map<string, ActiveEffect>;
staticDamageProfile?: StaticDamageProfile;
damageScratch: DamageScratch;
rng: Rng;
@@ -57,7 +58,6 @@
effectActivationCounts: Record<SideId, number>;
extraSkillAttackJobsByEffect: Record<string, number>;
attackControlCounts: { dodge: number; no_attack: number };
- consumedEffectUseKeys: Set<string>;
counters: {
attacks: Record<SideId, Record<UnitType, number>>;
received: Record<SideId, Record<UnitType, number>>;
@@ -78,6 +78,11 @@
effects: ActiveEffect[];
}
+interface ExtraSkillJobsResult {
+ jobs: DamageJob[];
+ consumedEffectIds: string[];
+}
+
interface BattleRun {
fighters: Record<SideId, ResolvedFighter>;
runtime: Runtime;
@@ -217,6 +222,7 @@
return {
activeEffects,
effectIndex,
+ effectById: new Map(activeEffects.map((e) => [e.id, e])),
staticDamageProfile: template.staticDamageProfile,
damageScratch: createFastDamageScratch(),
rng,
@@ -225,7 +231,6 @@
effectActivationCounts: { ...template.effectActivationCounts },
extraSkillAttackJobsByEffect: { ...template.extraSkillAttackJobsByEffect },
attackControlCounts: { ...template.attackControlCounts },
- consumedEffectUseKeys: new Set(),
counters: {
attacks: { attacker: { ...template.counters.attacks.attacker }, defender: { ...template.counters.attacks.defender } },
received: { attacker: { ...template.counters.received.attacker }, defender: { ...template.counters.received.defender } }
@@ -305,9 +310,13 @@
triggerRoundStartSkills(round, runtime, roundStartTroops);
const intents = resolveAttackIntents(round, runtime, roundStartTroops);
- const jobs: DamageJob[] = [];
+ const allJobs: DamageJob[] = []; // for recorder
+ const results: DamageJobResult[] = [];
const cancelled: CancelledAttack[] = [];
+ // Phase 1: fire all attack_declared triggers — all ActiveEffects are
+ // resolved before any damage is calculated, per the battle-core spec.
+ const pendingNormalJobs: DamageJob[] = [];
for (const intent of intents) {
triggerSkills("attack_declared", round, runtime.skills.attackDeclared, runtime, intent);
const job = normalJob(intent, roundStartTroops);
@@ -315,33 +324,54 @@
if (controls.no_attack || controls.dodge) {
const control = controls.no_attack ?? controls.dodge!;
runtime.attackControlCounts[control.reason] += 1;
- const consumedEffectIds = attackDurationEffectIdsForJob(job, round, runtime.activeEffects);
+ const consumedEffectIds = attackDurationEffectIdsForJob(job, runtime.effectIndex);
cancelled.push({ intent, effectId: control.effect.id, reason: control.reason, consumedEffectIds });
consumeEffects(runtime, consumedEffectIds);
} else {
- jobs.push(job);
- jobs.push(...extraSkillJobs(job, round, runtime, roundStartTroops));
+ pendingNormalJobs.push(job);
}
}
- const results: DamageJobResult[] = [];
- for (const job of jobs) {
- const result = calculateDamageJob(job, fighters, runtime.activeEffects, {
+ // Phase 2: calculate each normal job; any extra_skill_attack effect it
+ // uses spawns extra DamageJobs that are calculated immediately after.
+ // consumeEffects runs after each job so subsequent normal jobs see the
+ // correct uses count on any shared extra_skill_attack effects.
+ for (const job of pendingNormalJobs) {
+ allJobs.push(job);
+ const normalResult = calculateDamageJob(job, fighters, runtime.activeEffects, {
trace: recorder.capturesTrace,
effectIndex: runtime.effectIndex,
staticDamageProfile: runtime.staticDamageProfile,
scratch: recorder.capturesTrace ? undefined : runtime.damageScratch,
capToDefenderTroops: loopOptions.capJobKills
});
- results.push({ job, result });
- if (
- loopOptions.scoreSide &&
- job.attackerSide === loopOptions.scoreSide.attackerSide &&
- job.defenderSide === loopOptions.scoreSide.defenderSide
- ) {
- score += result.kills;
+ results.push({ job, result: normalResult });
+ if (loopOptions.scoreSide && job.attackerSide === loopOptions.scoreSide.attackerSide && job.defenderSide === loopOptions.scoreSide.defenderSide) {
+ score += normalResult.kills;
+ }
+ consumeEffects(runtime, normalResult.consumedEffectIds);
+
+ const extraSkill = extraSkillJobs(job, round, runtime, roundStartTroops);
+ if (extraSkill.consumedEffectIds.length > 0) {
+ normalResult.consumedEffectIds = [...normalResult.consumedEffectIds, ...extraSkill.consumedEffectIds];
+ consumeEffects(runtime, extraSkill.consumedEffectIds);
+ }
+
+ for (const extraJob of extraSkill.jobs) {
+ allJobs.push(extraJob);
+ const extraResult = calculateDamageJob(extraJob, fighters, runtime.activeEffects, {
+ trace: recorder.capturesTrace,
+ effectIndex: runtime.effectIndex,
+ staticDamageProfile: runtime.staticDamageProfile,
+ scratch: recorder.capturesTrace ? undefined : runtime.damageScratch,
+ capToDefenderTroops: loopOptions.capJobKills
+ });
+ results.push({ job: extraJob, result: extraResult });
+ if (loopOptions.scoreSide && extraJob.attackerSide === loopOptions.scoreSide.attackerSide && extraJob.defenderSide === loopOptions.scoreSide.defenderSide) {
+ score += extraResult.kills;
+ }
+ consumeEffects(runtime, extraResult.consumedEffectIds);
}
- consumeEffects(runtime, result.consumedEffectIds, result.consumedEffectUseKey, result.consumedEffectUseId, result.consumedEffectUseIds);
}
if (loopOptions.capRoundKills) capRoundKills(results, roundStartTroops);
@@ -350,7 +380,7 @@
for (const entry of cancelled) recorder.recordCancelled(entry.intent, entry.effectId, entry.reason, entry.consumedEffectIds);
for (const entry of results) recorder.recordDamageJob(entry.job, entry.result);
- recorder.recordRound(round, roundStartTroops, intents, jobs);
+ recorder.recordRound(round, roundStartTroops, intents, allJobs);
}
const winner = winnerFor(fighters) ?? "draw";
@@ -499,6 +529,7 @@
return {
activeEffects: [],
effectIndex: createEffectIndex(),
+ effectById: new Map(),
staticDamageProfile: undefined,
damageScratch: createFastDamageScratch(),
rng,
@@ -507,7 +538,6 @@
effectActivationCounts: { attacker: 0, defender: 0 },
extraSkillAttackJobsByEffect: {},
attackControlCounts: { dodge: 0, no_attack: 0 },
- consumedEffectUseKeys: new Set(),
counters: {
attacks: { attacker: emptyTroops(), defender: emptyTroops() },
received: { attacker: emptyTroops(), defender: emptyTroops() }
@@ -532,9 +562,23 @@
function addActiveEffect(runtime: Runtime, effect: ActiveEffect): void {
runtime.activeEffects.push(effect);
+ runtime.effectById.set(effect.id, effect);
indexEffect(runtime.effectIndex, effect);
}
+function expireInactive(runtime: Runtime, round: number): void {
+ let write = 0;
+ for (let read = 0; read < runtime.activeEffects.length; read += 1) {
+ const effect = runtime.activeEffects[read];
+ if (isEffectActive(effect, round)) {
+ runtime.activeEffects[write] = effect;
+ write += 1;
+ }
+ }
+ runtime.activeEffects.length = write;
+ pruneEffectIndex(runtime.effectIndex, (effect) => isEffectActive(effect, round));
+}
+
function addInputPassiveEffects(runtime: Runtime, passive: FighterInput["passive"], side: SideId): void {
if (!passive) return;
for (const stat of ["attack", "defense", "lethality", "health"] as const) {
@@ -697,8 +741,9 @@
round: number,
runtime: Runtime,
roundStartTroops: DamageJob["roundStartTroops"]
-): DamageJob[] {
+): ExtraSkillJobsResult {
const jobs: DamageJob[] = [];
+ const consumedEffectIds: string[] = [];
const effectGroups = selectStackedExtraAttackEffectGroups(
runtime.effectIndex.extraAttacks.filter((effect) => isEffectActive(effect, round) && extraAttackEffectAppliesToNormalAttack(effect, normalAttack)),
round
@@ -706,8 +751,6 @@
for (const effectGroup of effectGroups) {
const effect = effectGroup.selected;
const definitions = effect.triggerDamageJobs ?? [];
- const consumedEffectIds = effectGroup.effects.map((groupEffect) => groupEffect.id);
- const consumedEffectUseKey = `${normalAttack.sourceIntentId}:${effect.stackingKey ?? effect.id}`;
const firstJobIndex = jobs.length;
let definitionIndex = 0;
for (const definition of definitions) {
@@ -733,11 +776,7 @@
defenderUnit: target.unit,
sourceEffectId,
sourceSkillReportKey,
- sourceMultiplier: multiplier,
- consumedEffectIds,
- consumedEffectUseKey,
- consumedEffectUseId: effect.id,
- consumedEffectUseIds: consumedEffectIds
+ sourceMultiplier: multiplier
});
runtime.extraSkillAttackJobsByEffect[sourceEffectId] = (runtime.extraSkillAttackJobsByEffect[sourceEffectId] ?? 0) + 1;
}
@@ -745,10 +784,10 @@
definitionIndex += 1;
}
if (jobs.length > firstJobIndex) {
- for (const consumedEffectId of consumedEffectIds) reserveConsumedEffectUse(runtime, consumedEffectUseKey, consumedEffectId);
+ consumedEffectIds.push(...effectGroup.effects.map((groupEffect) => groupEffect.id));
}
}
- return jobs;
+ return { jobs, consumedEffectIds };
}
function selectStackedExtraAttackEffectGroups(effects: ActiveEffect[], round: number): ExtraAttackEffectGroup[] {
@@ -887,55 +926,23 @@
}
}
-function expireInactive(runtime: Runtime, round: number): void {
- let write = 0;
- for (let read = 0; read < runtime.activeEffects.length; read += 1) {
- const effect = runtime.activeEffects[read];
- if (isEffectActive(effect, round)) {
- runtime.activeEffects[write] = effect;
- write += 1;
- }
+function attackDurationEffectIdsForJob(job: DamageJob, index: EffectIndex): string[] {
+ const ids: string[] = [];
+ for (const candidate of bucketCandidatesForJob(index, job)) {
+ if (candidate.effect.duration.type === "attack") ids.push(candidate.effect.id);
}
- runtime.activeEffects.length = write;
- pruneEffectIndex(runtime.effectIndex, (effect) => isEffectActive(effect, round));
-}
-
-function attackDurationEffectIdsForJob(job: DamageJob, round: number, effects: ActiveEffect[]): string[] {
- return effects
- .filter((effect) => {
- if (effect.kind === "extra_attack" || effect.duration.type !== "attack" || !isEffectActive(effect, round)) return false;
- const classification = classifyEffectForJob(effect, job);
- return classification?.kind === "bucket" || classification?.kind === "control";
- })
- .map((effect) => effect.id);
-}
-
-function consumeEffects(
- runtime: Runtime,
- consumedEffectIds: string[],
- consumedEffectUseKey?: string,
- consumedEffectUseId?: string,
- consumedEffectUseIds?: string[]
-): void {
- const dedupeIds = consumedEffectUseIds ?? (consumedEffectUseId ? [consumedEffectUseId] : undefined);
- for (const consumed of consumedEffectIds) {
- if (consumedEffectUseKey && dedupeIds?.includes(consumed)) {
- const useKey = `${consumedEffectUseKey}:${consumed}`;
- if (runtime.consumedEffectUseKeys.has(useKey)) continue;
- runtime.consumedEffectUseKeys.add(useKey);
- }
- for (const effect of runtime.activeEffects) {
- if (effect.id === consumed) effect.uses += 1;
- }
+ for (const effect of index.controls) {
+ if (effect.duration.type !== "attack") continue;
+ const classification = classifyEffectForJob(effect, job);
+ if (classification?.kind === "control") ids.push(effect.id);
}
+ return ids;
}
-function reserveConsumedEffectUse(runtime: Runtime, consumedEffectUseKey: string, consumedEffectId: string): void {
- const useKey = `${consumedEffectUseKey}:${consumedEffectId}`;
- if (runtime.consumedEffectUseKeys.has(useKey)) return;
- runtime.consumedEffectUseKeys.add(useKey);
- for (const effect of runtime.activeEffects) {
- if (effect.id === consumedEffectId) effect.uses += 1;
+function consumeEffects(runtime: Runtime, consumedEffectIds: string[]): void {
+ for (const consumed of consumedEffectIds) {
+ const effect = runtime.effectById.get(consumed);
+ if (effect) effect.uses += 1;
}
}
diff --git a/simulator/src/types.ts b/simulator/src/types.ts
--- a/simulator/src/types.ts
+++ b/simulator/src/types.ts
@@ -278,10 +278,6 @@
sourceEffectId?: string;
sourceSkillReportKey?: string;
sourceMultiplier?: number;
- consumedEffectIds?: string[];
- consumedEffectUseKey?: string;
- consumedEffectUseId?: string;
- consumedEffectUseIds?: string[];
}
export interface CounterDelta {
@@ -344,9 +340,6 @@
appliedEffectIds: string[];
appliedEffects: AppliedEffectTrace[];
consumedEffectIds: string[];
- consumedEffectUseKey?: string;
- consumedEffectUseId?: string;
- consumedEffectUseIds?: string[];
cancelledBy?: string;
cancelReason?: "dodge" | "no_attack";
trace?: DamageEquationTrace;