WOS Sim
← Back to Runs

Compare Runs

A (baseline)
a3d24497-d8ae1679b6904/08/2026, 01:18:58
B (current)
7936ff16-05a92a8e53d06/08/2026, 16:47:41
Avg Error A0.18%
Avg Error B0.18%
Δ Avg Error0.00%
Changed0
Improved0
Regressed0
Added0
Retired0

Testcase Delta

267 / 267
FileCase#AdjA%B%Δ%Status
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_123+0.033%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_134+0.05%-0.04%-0.04%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_145+0.01%0.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.03%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_178+0.012%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_189+0.025%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.036%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2112+0.017%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2213+0.013%0.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.042%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2516+0.008%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_2617+0.014%0.00%0.00%0.00%unchanged
testcases/1-testcases_no-heroes_t6_single-type_nc.jsondaut_viper_90+0.026%0.00%0.00%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_10+0.05%-0.38%-0.38%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.43%-0.43%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_43+0.05%-0.56%-0.56%0.00%unchanged
testcases/2-testcases_no-heroes_t6_mixed_nc.jsondaut_viper_54+0.05%-0.08%-0.08%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_100.00%0.00%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_100.18%0.18%0.00%unchanged
testcases/emulator_verified/ahmose_solo_nc.jsonahmose_solo0+0.025%0.00%0.00%0.00%unchanged
testcases/emulator_verified/alonso_attacker_600_all.jsonalonso_attacker_600_all0-0.12%-0.12%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.30%0.30%0.00%unchanged
testcases/emulator_verified/alonso_norah_combo.jsonalonso_norah_combo0-0.03%-0.03%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.82%2.82%0.00%unchanged
testcases/emulator_verified/alonso_solo_v2.jsonalonso_solo_v201.62%1.62%0.00%unchanged
testcases/emulator_verified/alonso_solo_v2.jsonalonso_solo_v21-1.29%-1.29%0.00%unchanged
testcases/emulator_verified/alonso_vs_alonso.jsonalonso_vs_alonso0-0.01%-0.01%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_attacker00.04%0.04%0.00%unchanged
testcases/emulator_verified/attdef_jasser_patrick_minxxx_attacks_nc.jsonattdef_jasser_patrick_minxxx_attacks00.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_attacks00.00%0.00%0.00%unchanged
testcases/emulator_verified/attdef_jasser_patrick_wip_attacks_nc.jsonattdef_jasser_patrick_wip_attacks10.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.40%-3.40%0.00%unchanged
testcases/emulator_verified/bahiti_solo.jsonbahiti_solo00.26%0.26%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_normal0+0.025%0.00%0.00%0.00%unchanged
testcases/emulator_verified/determinism_test_swapped_nc.jsondeterminism_test_swapped0-0.025%0.00%0.00%0.00%unchanged
testcases/emulator_verified/flint_reina_zinman_combo.jsonflint_reina_zinman_combo0-0.14%-0.14%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.56%-1.56%0.00%unchanged
testcases/emulator_verified/greg_mia_combo.jsongreg_mia_combo1-0.05%1.11%1.11%0.00%unchanged
testcases/emulator_verified/greg_mia_defender_current.jsongreg_mia_defender_current0-0.01%-0.01%0.00%unchanged
testcases/emulator_verified/greg_mia_nohero_control_current.jsongreg_mia_nohero_control_current0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/greg_mia_nohero_control_current.jsongreg_mia_nohero_control_current1+0.05%-0.02%-0.02%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.00%0.00%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.15%0.15%0.00%unchanged
testcases/emulator_verified/gwen_norah_combo_nc.jsongwen_norah_combo_nc00.19%0.19%0.00%unchanged
testcases/emulator_verified/gwen_solo_nc.jsongwen_solo0+0.05%-0.04%-0.04%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.46%-0.46%0.00%unchanged
testcases/emulator_verified/hector_solo.jsonhector_solo00.11%0.11%0.00%unchanged
testcases/emulator_verified/jasser_solo_nc.jsonjasser_solo0+0.05%-0.02%-0.02%0.00%unchanged
testcases/emulator_verified/jessie_solo_nc.jsonjessie_solo0-0.02%-0.43%-0.43%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.54%0.54%0.00%unchanged
testcases/emulator_verified/logan_gwen_combo_nc.jsonlogan_gwen_combo_nc00.00%0.00%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.02%0.02%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.02%-0.02%0.00%unchanged
testcases/emulator_verified/lynn_solo.jsonlynn_solo0-0.06%-0.06%0.00%unchanged
testcases/emulator_verified/mia_only_defender_current.jsonmia_only_defender_current00.01%0.01%0.00%unchanged
testcases/emulator_verified/mia_only_defender_current.jsonmia_only_defender_current1-0.13%-0.13%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo0-0.19%-0.19%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo10.23%0.23%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo2-0.15%-0.15%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo30.03%0.03%0.00%unchanged
testcases/emulator_verified/mia_solo.jsonmia_solo40.27%0.27%0.00%unchanged
testcases/emulator_verified/molly_lynn_combo.jsonmolly_lynn_combo00.01%0.01%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.08%-1.08%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.08%0.08%0.00%unchanged
testcases/emulator_verified/norah_s2_inf_only_A.jsonnorah_s2_inf_only_A0+0.05%-0.32%-0.32%0.00%unchanged
testcases/emulator_verified/norah_s2_inf_only_B_nc.jsonnorah_s2_inf_only_B0+0.033%-0.24%-0.24%0.00%unchanged
testcases/emulator_verified/norah_s2_splash_A.jsonnorah_s2_splash_A00.21%0.21%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_C0+0.05%-0.06%-0.06%0.00%unchanged
testcases/emulator_verified/norah_s2_splash_D.jsonnorah_s2_splash_D00.02%0.02%0.00%unchanged
testcases/emulator_verified/norah_solo.jsonnorah_solo0-0.30%-0.30%0.00%unchanged
testcases/emulator_verified/norah_zinman_combo_nc.jsonnorah_zinman_combo_nc00.09%0.09%0.00%unchanged
testcases/emulator_verified/patrick_solo_nc.jsonpatrick_solo0+0.004%-0.04%-0.04%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.20%0.20%0.00%unchanged
testcases/emulator_verified/philly_solo.jsonphilly_solo00.16%0.16%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.15%0.15%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.62%-1.62%0.00%unchanged
testcases/emulator_verified/sergey_reina_combo_nc.jsonsergey_reina_combo_nc0-0.37%-0.37%0.00%unchanged
testcases/emulator_verified/sergey_solo_nc.jsonsergey_solo00.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_nc00.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_solo00.02%0.02%0.00%unchanged
testcases/emulator_verified/wayne_mixed_solo.jsonwayne_mixed_solo1-1.87%-1.87%0.00%unchanged
testcases/emulator_verified/wayne_reina_combo.jsonwayne_reina_combo0-0.38%-0.38%0.00%unchanged
testcases/emulator_verified/wayne_reina_wuming_combo.jsonwayne_reina_wuming_combo00.19%0.19%0.00%unchanged
testcases/emulator_verified/wayne_s1_solo.jsonwayne_s1_solo00.19%0.19%0.00%unchanged
testcases/emulator_verified/wayne_s2_solo.jsonwayne_s2_solo00.36%0.36%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.03%-0.03%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.06%-0.06%0.00%unchanged
testcases/emulator_verified/wos427_wip_sergey_philly_840.jsonwos427_wip_sergey_philly_8400-0.87%-0.87%0.00%unchanged
testcases/emulator_verified/wos444_bradley_s3_all_troops_damage_control_nc.jsonwos444_bradley_s3_all_troops_damage_control_nc00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_bradley_s3_all_troops_damage_control_nc.jsonwos444_bradley_s3_all_troops_damage_control_nc1+0.025%0.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_nc1+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/wos444_edith_s11_mark_damage_taken_hero_nc.jsonwos444_edith_s11_mark_damage_taken_hero_nc0+0.05%0.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_nc0+0.05%0.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_nc10.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.09%0.09%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_control00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s22_infantry_damage_up_bucket.jsonwos451_bradley_s22_infantry_damage_up_bucket00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_bradley_s22_infantry_damage_up_control.jsonwos451_bradley_s22_infantry_damage_up_control0+0.025%0.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_bucket.jsonwos451_gordon_s11_lancer_damage_up_bucket0-0.05%0.06%0.06%0.00%unchanged
testcases/emulator_verified/wos451_gordon_s11_lancer_damage_up_control.jsonwos451_gordon_s11_lancer_damage_up_control00.00%0.00%0.00%unchanged
testcases/emulator_verified/wos451_gordon_s21_lancer_damage_up_bucket.jsonwos451_gordon_s21_lancer_damage_up_bucket0-0.05%0.04%0.04%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_solo0+0.05%0.00%0.00%0.00%unchanged
testcases/emulator_verified/zinman_solo_nc.jsonzinman_solo0+0.05%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s1-4900-vs-1000.jsons1-4900-vs-100000.00%0.00%0.00%unchanged
testcases/gatot_verified/s1-4950-vs-1000.jsons1-4950-vs-10000-0.021%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s1-5000-vs-1000.jsons1-5000-vs-10000-0.015%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s1-5050-vs-1000.jsons1-5050-vs-10000-0.011%-0.02%-0.02%0.00%unchanged
testcases/gatot_verified/s10-bradley-1000-inf-125-marksman-vs-5000-inf.jsons10-bradley-1000-inf-125-marksman-vs-5000-inf0+0.05%-0.10%-0.10%0.00%unchanged
testcases/gatot_verified/s15.2-secondary-heroes-1000-inf-125-lancer-125-marksman.jsons15.2-secondary-heroes-1000-inf-125-lancer-125-marksman0+0.05%-0.06%-0.06%0.00%unchanged
testcases/gatot_verified/s15.3-1000-inf-0-lancer-1000-marksman.jsons15.3-1000-inf-0-lancer-1000-marksman0-0.05%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s15.3-1000-inf-1000-lancer-0-marksman.jsons15.3-1000-inf-1000-lancer-0-marksman0-0.05%0.01%0.01%0.00%unchanged
testcases/gatot_verified/s15.3-1000-inf-1000-lancer-1000-marksman.jsons15.3-1000-inf-1000-lancer-1000-marksman0-0.05%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s15.3-2000-inf-0-lancer-1000-marksman.jsons15.3-2000-inf-0-lancer-1000-marksman0-0.031%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s15.3-2000-inf-1000-lancer-0-marksman.jsons15.3-2000-inf-1000-lancer-0-marksman0-0.043%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s16-1500-inf-1500-marksman-vs-1000-inf.jsons16-1500-inf-1500-marksman-vs-1000-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s16-2000-inf-2000-marksman-vs-1000-inf.jsons16-2000-inf-2000-marksman-vs-1000-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s16-2000-inf-2000-marksman-vs-5000-inf.jsons16-2000-inf-2000-marksman-vs-5000-inf0-0.005%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s16-7500-inf-vs-5000-inf.jsons16-7500-inf-vs-5000-inf0+0.01%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s2-health-1.1-4950-vs-1000.jsons2-health-1.1-4950-vs-10000-0.022%0.02%0.02%0.00%unchanged
testcases/gatot_verified/s2-health-1.1-5000-vs-1000.jsons2-health-1.1-5000-vs-10000-0.016%0.02%0.02%0.00%unchanged
testcases/gatot_verified/s2-health-1.1-5050-vs-1000.jsons2-health-1.1-5050-vs-10000-0.011%0.02%0.02%0.00%unchanged
testcases/gatot_verified/s2-health-1.1-5100-vs-1000.jsons2-health-1.1-5100-vs-10000-0.008%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s2-health-1.1-5150-vs-1000.jsons2-health-1.1-5150-vs-100000.00%0.00%0.00%unchanged
testcases/gatot_verified/s25-1000-inf-3000-marksman-vs-5000-inf.jsons25-1000-inf-3000-marksman-vs-5000-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s25-2000-inf-2000-marksman-vs-2000-inf.jsons25-2000-inf-2000-marksman-vs-2000-inf0+0.05%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s25-2000-inf-2000-marksman-vs-3000-inf.jsons25-2000-inf-2000-marksman-vs-3000-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s25-2000-inf-2000-marksman-vs-4000-inf.jsons25-2000-inf-2000-marksman-vs-4000-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s25-3000-inf-1000-marksman-vs-5000-inf.jsons25-3000-inf-1000-marksman-vs-5000-inf0+0.018%0.01%0.01%0.00%unchanged
testcases/gatot_verified/s3-5000-inf-vs-1000-inf-10-lancer.jsons3-5000-inf-vs-1000-inf-10-lancer0-0.05%7.04%7.04%0.00%unchanged
testcases/gatot_verified/s31-current-2000-inf-2000-marksman-vs-5000-inf.jsons31-current-2000-inf-2000-marksman-vs-5000-inf0+0.017%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s32-current-750-marksman-wayne-vs-3000-inf.jsons32-current-750-marksman-wayne-vs-3000-inf0-0.05%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s33-current-500-marksman-wayne3-vs-5000-gatot3-inf.jsons33-current-500-marksman-wayne3-vs-5000-gatot3-inf00.10%0.10%0.00%unchanged
testcases/gatot_verified/s34-current-2100-lancer-2100-marksman-vs-1100-gatot3-inf.jsons34-current-2100-lancer-2100-marksman-vs-1100-gatot3-inf0-0.017%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s35-current-3000-lancer-1000-marksman-vs-1100-gatot3-inf.jsons35-current-3000-lancer-1000-marksman-vs-1100-gatot3-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s36-current-600-lancer-600-marksman-vs-1100-gatot3-inf.jsons36-current-600-lancer-600-marksman-vs-1100-gatot3-inf0-0.05%0.00%0.00%0.00%unchanged
testcases/gatot_verified/s37-current-bilateral-gatot-2000-inf-2000-marksman-vs-5000-inf.jsons37-current-bilateral-gatot-2000-inf-2000-marksman-vs-5000-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s38-current-bilateral-gatot-800-inf-800-marksman-vs-1100-inf.jsons38-current-bilateral-gatot-800-inf-800-marksman-vs-1100-inf0-0.05%0.04%0.04%0.00%unchanged
testcases/gatot_verified/s39-1000-lancer-400-marksman-vs-500-gatot3-inf.jsons39-1000-lancer-400-marksman-vs-500-gatot3-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s4-fc9-gatot-10000.jsons4-fc9-gatot-1000000.01%0.01%0.00%unchanged
testcases/gatot_verified/s4-fc9-gatot-20000.jsons4-fc9-gatot-2000000.01%0.01%0.00%unchanged
testcases/gatot_verified/s4-fc9-gatot-40000.jsons4-fc9-gatot-4000000.14%0.14%0.00%unchanged
testcases/gatot_verified/s4-fc9-gatot-90000.jsons4-fc9-gatot-900000-0.11%-0.11%0.00%unchanged
testcases/gatot_verified/s40-2000-inf-1000-marksman-vs-5000-inf.jsons40-2000-inf-1000-marksman-vs-5000-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s41-300-inf-200-lancer-600-marksman-vs-500-inf.jsons41-300-inf-200-lancer-600-marksman-vs-500-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s42-current-wayne-skill-shield-scope-200-inf-vs-450-inf.jsons42-current-wayne-skill-shield-scope-200-inf-vs-450-inf00.13%0.13%0.00%unchanged
testcases/gatot_verified/s43-current-reciprocal-shield-cap-450-inf-vs-200-inf.jsons43-current-reciprocal-shield-cap-450-inf-vs-200-inf00.00%0.00%0.00%unchanged
testcases/gatot_verified/s44-current-reciprocal-shield-cap-2500-inf-vs-500-inf.jsons44-current-reciprocal-shield-cap-2500-inf-vs-500-inf0+0.05%0.03%0.03%0.00%unchanged
testcases/gatot_verified/s5-fc9-gatot-lumak-20000.jsons5-fc9-gatot-lumak-2000000.01%0.01%0.00%unchanged
testcases/gatot_verified/s5-fc9-gatot-lumak-40000.jsons5-fc9-gatot-lumak-4000000.00%0.00%0.00%unchanged
testcases/gatot_verified/s5-fc9-gatot-lumak-90000.jsons5-fc9-gatot-lumak-9000000.01%0.01%0.00%unchanged
testcases/gatot_verified/s7-10000-t6-marksmen-vs-one-t1-fc10-infantry.jsons7-10000-t6-marksmen-vs-one-t1-fc10-infantry0-0.02%-0.02%0.00%unchanged
testcases/gatot_verified/s8-10000-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-10000-t9-marksmen-vs-one-t1-fc10-infantry0-0.02%-0.02%0.00%unchanged
testcases/gatot_verified/s8-11548-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-11548-t9-marksmen-vs-one-t1-fc10-infantry0-0.05%0.87%0.87%0.00%unchanged
testcases/gatot_verified/s8-11791-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-11791-t9-marksmen-vs-one-t1-fc10-infantry0-0.05%0.61%0.61%0.00%unchanged
testcases/gatot_verified/s8-12000-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-12000-t9-marksmen-vs-one-t1-fc10-infantry00.42%0.42%0.00%unchanged
testcases/gatot_verified/s8-20000-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-20000-t9-marksmen-vs-one-t1-fc10-infantry00.00%0.00%0.00%unchanged
testcases/gatot_verified/s8-25000-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-25000-t9-marksmen-vs-one-t1-fc10-infantry00.14%0.14%0.00%unchanged
testcases/gatot_verified/s8-30116-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-30116-t9-marksmen-vs-one-t1-fc10-infantry00.02%0.02%0.00%unchanged
testcases/gatot_verified/s8-30139-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-30139-t9-marksmen-vs-one-t1-fc10-infantry0-0.02%-0.02%0.00%unchanged
testcases/gatot_verified/s8-30179-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-30179-t9-marksmen-vs-one-t1-fc10-infantry00.04%0.04%0.00%unchanged
testcases/gatot_verified/s8-30205-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-30205-t9-marksmen-vs-one-t1-fc10-infantry0-0.01%-0.01%0.00%unchanged
testcases/gatot_verified/s8-30228-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-30228-t9-marksmen-vs-one-t1-fc10-infantry0-0.02%-0.02%0.00%unchanged
testcases/gatot_verified/s8-33427-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-33427-t9-marksmen-vs-one-t1-fc10-infantry00.00%0.00%0.00%unchanged
testcases/gatot_verified/s8-33452-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-33452-t9-marksmen-vs-one-t1-fc10-infantry00.00%0.00%0.00%unchanged
testcases/gatot_verified/s8-33481-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-33481-t9-marksmen-vs-one-t1-fc10-infantry00.01%0.01%0.00%unchanged
testcases/gatot_verified/s8-33510-t9-marksmen-vs-one-t1-fc10-infantry.jsons8-33510-t9-marksmen-vs-one-t1-fc10-infantry00.01%0.01%0.00%unchanged
testcases/gatot_verified/s9-1500-t9-lancers-vs-146-t1-fc10-lancers.jsons9-1500-t9-lancers-vs-146-t1-fc10-lancers00.01%0.01%0.00%unchanged
testcases/gatot_verified/s9-1950-t9-lancers-vs-146-t1-fc10-lancers.jsons9-1950-t9-lancers-vs-146-t1-fc10-lancers0-0.07%-0.07%0.00%unchanged
testcases/gatot_verified/s9-2100-t9-lancers-vs-146-t1-fc10-lancers.jsons9-2100-t9-lancers-vs-146-t1-fc10-lancers00.55%0.55%0.00%unchanged
testcases/gatot_verified/s9-2200-t9-lancers-vs-146-t1-fc10-lancers.jsons9-2200-t9-lancers-vs-146-t1-fc10-lancers01.99%1.99%0.00%unchanged
testcases/gatot_verified/s9-2201-t9-lancers-vs-146-t1-fc10-lancers.jsons9-2201-t9-lancers-vs-146-t1-fc10-lancers0-0.72%-0.72%0.00%unchanged
testcases/gatot_verified/s9-3000-t9-lancers-vs-146-t1-fc10-lancers.jsons9-3000-t9-lancers-vs-146-t1-fc10-lancers00.15%0.15%0.00%unchanged
testcases/heroes_unittests/Flint_tc.jsondaut_viper_10+0.05%-0.01%-0.01%0.00%unchanged
testcases/heroes_unittests/Flint_tc.jsondaut_viper_21-0.05%0.37%0.37%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.033%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Jessie_tc_nc.jsondaut_viper_54+0.05%0.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.22%0.22%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_21+0.05%-0.68%-0.68%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_320.08%0.08%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_43-0.35%-0.35%0.00%unchanged
testcases/heroes_unittests/Mia_tc.jsondaut_viper_54-0.10%-0.10%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.35%0.35%0.00%unchanged
testcases/heroes_unittests/Molly_tc.jsondaut_viper_54-0.01%-0.01%0.00%unchanged
testcases/heroes_unittests/Reina_tc.jsondaut_viper_10-0.05%0.00%0.00%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.62%-0.62%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.08%-0.08%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.05%0.00%0.00%0.00%unchanged
testcases/heroes_unittests/Seo-yoon_tc_nc.jsondaut_viper_76+0.033%0.00%0.00%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.009%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 (e1679b6992a8e53d)

92a8e53dtidy uppiddlyminx06/08/2026, 16:47:07

Code Changes (Run A → Run B)

Index: simulator/config/hero_definitions/Blanchette.json
===================================================================
--- simulator/config/hero_definitions/Blanchette.json prev run
+++ simulator/config/hero_definitions/Blanchette.json this run
@@ -0,0 +1,86 @@
+{
+ "name": "Blanchette",
+ "hero_generation": "S10",
+ "troop_type": "marksman",
+ "skills": {
+ "ArmedToTheTeeth": {
+ "description": "Blanchette increases all Troops' Lethality by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "ArmedToTheTeeth/1": {
+ "type": "active.hero.lethality.up",
+ "value": [5, 10, 15, 20, 25]
+ }
+ }
+ },
+ "BloodHunter": {
+ "description": "Blanchette's Marksmen fire a crystal blade every 3 rounds, dealing X% extra damage to their target.",
+ "trigger": {
+ "type": "turn",
+ "every": 3,
+ "source": "marksman"
+ },
+ "effects": {
+ "BloodHunter/1": {
+ "type": "extra_skill_attack",
+ "value": [15, 30, 45, 60, 75],
+ "units": {
+ "applies_to": "trigger.source",
+ "applies_vs": "trigger.target"
+ },
+ "duration": { "attacks": { "count": 1 } },
+ "trigger_damage_jobs": [
+ { "source": "use.source", "target": "use.target" }
+ ]
+ }
+ }
+ },
+ "CrimsonSniper": {
+ "description": "Every 2 strikes, Blanchette's Marksmen deal X% extra damage to enemy Lancers and Y% extra damage to enemy Marksmen.",
+ "trigger": {
+ "type": "attack",
+ "every": 2,
+ "source": "marksman"
+ },
+ "effects": {
+ "CrimsonSniper/1": {
+ "type": "extra_skill_attack",
+ "value": [8, 16, 24, 32, 40],
+ "units": {
+ "applies_to": "trigger.source",
+ "applies_vs": "any"
+ },
+ "duration": { "attacks": { "count": 1 } },
+ "trigger_damage_jobs": [
+ { "source": "use.source", "target": ["lancer"] }
+ ]
+ },
+ "CrimsonSniper/2": {
+ "type": "extra_skill_attack",
+ "value": [4, 8, 12, 16, 20],
+ "units": {
+ "applies_to": "trigger.source",
+ "applies_vs": "any"
+ },
+ "duration": { "attacks": { "count": 1 } },
+ "trigger_damage_jobs": [
+ { "source": "use.source", "target": ["marksman"] }
+ ]
+ }
+ }
+ },
+ "LightningStrike": {
+ "description": "Widget bonus: Increase Lethality by X% for all troops when rallying.",
+ "trigger": { "type": "pre_battle" },
+ "requirements": [
+ { "level": 1, "type": "engagement_type", "value": "rally" }
+ ],
+ "effects": {
+ "LightningStrike/1": {
+ "type": "passive.lethality.up",
+ "value": [5, 7.5, 10, 12.5, 15]
+ }
+ }
+ }
+ }
+}
Index: simulator/config/hero_definitions/Fred.json
===================================================================
--- simulator/config/hero_definitions/Fred.json prev run
+++ simulator/config/hero_definitions/Fred.json this run
@@ -0,0 +1,71 @@
+{
+ "name": "Fred",
+ "hero_generation": "S9",
+ "troop_type": "lancer",
+ "skills": {
+ "HydraulicSuppression": {
+ "description": "Fred's water volleys destroy opponent momentum, reducing all enemy troops' Lethality by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "HydraulicSuppression/1": {
+ "type": "active.hero.lethality.down",
+ "value": [4, 8, 12, 16, 20],
+ "units": { "applies_to": "enemy.any" }
+ }
+ }
+ },
+ "Acidification": {
+ "description": "Fred coats enemy Infantry shields with a special acidic blend, amplifying their damage taken by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "Acidification/1": {
+ "type": "active.hero.damageTaken.up",
+ "value": [4, 8, 12, 16, 20],
+ "units": { "applies_to": "enemy.infantry" }
+ }
+ }
+ },
+ "Floodbringer": {
+ "description": "Fred's Lancers deal X% additional damage every 4 strikes and reduce enemy troop damage dealt by Y% on the next turn.",
+ "trigger": {
+ "type": "attack",
+ "every": 4,
+ "source": "lancer"
+ },
+ "effects": {
+ "Floodbringer/1": {
+ "type": "extra_skill_attack",
+ "value": [40, 80, 120, 160, 200],
+ "units": {
+ "applies_to": "trigger.source",
+ "applies_vs": "trigger.target"
+ },
+ "duration": { "attacks": { "count": 1 } },
+ "trigger_damage_jobs": [
+ { "source": "use.source", "target": "use.target" }
+ ]
+ },
+ "Floodbringer/2": {
+ "type": "active.hero.damage.down",
+ "value": [4, 8, 12, 16, 20],
+ "units": { "applies_to": "enemy.any" },
+ "duration": { "turns": { "delay": 1, "count": 1 } },
+ "same_effect_stacking": "max"
+ }
+ }
+ },
+ "CallOfTheFirefighter": {
+ "description": "Widget bonus: Increase Attack by X% for all troops when rallying.",
+ "trigger": { "type": "pre_battle" },
+ "requirements": [
+ { "level": 1, "type": "engagement_type", "value": "rally" }
+ ],
+ "effects": {
+ "CallOfTheFirefighter/1": {
+ "type": "passive.attack.up",
+ "value": [5, 7.5, 10, 12.5, 15]
+ }
+ }
+ }
+ }
+}
Index: simulator/config/hero_definitions/Freya.json
===================================================================
--- simulator/config/hero_definitions/Freya.json prev run
+++ simulator/config/hero_definitions/Freya.json this run
@@ -0,0 +1,69 @@
+{
+ "name": "Freya",
+ "hero_generation": "S10",
+ "troop_type": "lancer",
+ "skills": {
+ "FogOfWar": {
+ "description": "Freya reduces all enemy Troops' Attack by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "FogOfWar/1": {
+ "type": "active.hero.attack.down",
+ "value": [4, 8, 12, 16, 20],
+ "units": { "applies_to": "enemy.any" }
+ }
+ }
+ },
+ "BloodMoonScythe": {
+ "description": "After her Lancers launch a normal attack, Freya has a 50% chance of performing Reap for X% damage.",
+ "trigger": {
+ "type": "attack",
+ "probability": [50, 50, 50, 50, 50],
+ "source": "lancer"
+ },
+ "effects": {
+ "BloodMoonScythe/1": {
+ "type": "extra_skill_attack",
+ "value": [20, 40, 60, 80, 100],
+ "units": {
+ "applies_to": "trigger.source",
+ "applies_vs": "trigger.target"
+ },
+ "duration": { "attacks": { "count": 1 } },
+ "trigger_damage_jobs": [
+ { "source": "use.source", "target": "use.target" }
+ ]
+ }
+ }
+ },
+ "NightsVengeance": {
+ "description": "Freya reduces damage taken by X% and increases damage dealt by X% for friendly Infantry and Marksmen.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "NightsVengeance/1": {
+ "type": "active.hero.damageTaken.down",
+ "value": [3, 6, 9, 12, 15],
+ "units": { "applies_to": ["infantry", "marksman"] }
+ },
+ "NightsVengeance/2": {
+ "type": "active.hero.damage.up",
+ "value": [3, 6, 9, 12, 15],
+ "units": { "applies_to": ["infantry", "marksman"] }
+ }
+ }
+ },
+ "DefenderOfTheWatch": {
+ "description": "Widget bonus: Increase Defense by X% for all troops when defending.",
+ "trigger": { "type": "pre_battle" },
+ "requirements": [
+ { "level": 1, "type": "engagement_type", "value": "garrison" }
+ ],
+ "effects": {
+ "DefenderOfTheWatch/1": {
+ "type": "passive.defense.up",
+ "value": [5, 7.5, 10, 12.5, 15]
+ }
+ }
+ }
+ }
+}
Index: simulator/config/hero_definitions/Gregory.json
===================================================================
--- simulator/config/hero_definitions/Gregory.json prev run
+++ simulator/config/hero_definitions/Gregory.json this run
@@ -0,0 +1,66 @@
+{
+ "name": "Gregory",
+ "hero_generation": "S10",
+ "troop_type": "infantry",
+ "skills": {
+ "LegionOfTheSun": {
+ "description": "Gregory increases Attack by X% and Defense by Y% for all troops.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "LegionOfTheSun/1": {
+ "type": "active.hero.attack.up",
+ "value": [3, 6, 9, 12, 15]
+ },
+ "LegionOfTheSun/2": {
+ "type": "active.hero.defense.up",
+ "value": [2, 4, 6, 8, 10]
+ }
+ }
+ },
+ "ChargedAssault": {
+ "description": "Gregory grants all troops' normal attacks an X% chance of dealing critical damage.",
+ "trigger": {
+ "type": "attack",
+ "probability": [5, 10, 15, 20, 25]
+ },
+ "effects": {
+ "ChargedAssault/1": {
+ "type": "extra_skill_attack",
+ "value": [100, 100, 100, 100, 100],
+ "units": {
+ "applies_to": "trigger.source",
+ "applies_vs": "trigger.target"
+ },
+ "duration": { "attacks": { "count": 1 } },
+ "trigger_damage_jobs": [
+ { "source": "use.source", "target": "use.target" }
+ ]
+ }
+ }
+ },
+ "Unbroken": {
+ "description": "Gregory reduces Infantry Damage Taken by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "Unbroken/1": {
+ "type": "active.hero.damageTaken.down",
+ "value": [4, 8, 12, 16, 20],
+ "units": { "applies_to": ["infantry"] }
+ }
+ }
+ },
+ "DayOfTheGuard": {
+ "description": "Widget bonus: Increase Lethality by X% for all troops when defending.",
+ "trigger": { "type": "pre_battle" },
+ "requirements": [
+ { "level": 1, "type": "engagement_type", "value": "garrison" }
+ ],
+ "effects": {
+ "DayOfTheGuard/1": {
+ "type": "passive.lethality.up",
+ "value": [5, 7.5, 10, 12.5, 15]
+ }
+ }
+ }
+ }
+}
Index: simulator/config/hero_definitions/Magnus.json
===================================================================
--- simulator/config/hero_definitions/Magnus.json prev run
+++ simulator/config/hero_definitions/Magnus.json this run
@@ -0,0 +1,63 @@
+{
+ "name": "Magnus",
+ "hero_generation": "S9",
+ "troop_type": "infantry",
+ "skills": {
+ "Rapacious": {
+ "description": "Magnus rouses his troops with brutal intensity, boosting friendly Troop Attack by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "Rapacious/1": {
+ "type": "active.hero.attack.up",
+ "value": [5, 10, 15, 20, 25]
+ }
+ }
+ },
+ "IronPhalanx": {
+ "description": "Infantry under Magnus' command have a 40% chance of gaining X% Defense when attacking for 1 turn.",
+ "trigger": {
+ "type": "attack",
+ "probability": [40, 40, 40, 40, 40],
+ "source": "infantry"
+ },
+ "effects": {
+ "IronPhalanx/1": {
+ "type": "active.hero.defense.up",
+ "value": [10, 20, 30, 40, 50],
+ "units": { "applies_to": "trigger.source" },
+ "duration": { "turns": { "count": 1 } },
+ "same_effect_stacking": "max"
+ }
+ }
+ },
+ "Iceman": {
+ "description": "Magnus reduces damage taken by friendly Infantry by X% while boosting friendly Marksmen damage by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "Iceman/1": {
+ "type": "active.hero.damageTaken.down",
+ "value": [2, 4, 6, 8, 10],
+ "units": { "applies_to": ["infantry"] }
+ },
+ "Iceman/2": {
+ "type": "active.hero.damage.up",
+ "value": [2, 4, 6, 8, 10],
+ "units": { "applies_to": ["marksman"] }
+ }
+ }
+ },
+ "ValoricInspiration": {
+ "description": "Widget bonus: Increase Health by X% for all troops when defending.",
+ "trigger": { "type": "pre_battle" },
+ "requirements": [
+ { "level": 1, "type": "engagement_type", "value": "garrison" }
+ ],
+ "effects": {
+ "ValoricInspiration/1": {
+ "type": "passive.health.up",
+ "value": [5, 7.5, 10, 12.5, 15]
+ }
+ }
+ }
+ }
+}
Index: simulator/config/hero_definitions/README.md
===================================================================
--- simulator/config/hero_definitions/README.md prev run
+++ simulator/config/hero_definitions/README.md this run
@@ -60,9 +60,13 @@
### Filename, `name`, aliases, and registration
The filename without `.json` is the definition's primary config key. The filename, `name`, and every entry in `aliases` can all resolve the hero after normalization. For example, `WuMing.json` can have `"name": "Wu Ming"`; both resolve to the same definition.
-The Node directory loader discovers JSON files automatically. The browser-safe/default config in `simulator/src/config.ts` uses explicit JSON imports, so adding a new file also requires importing it and adding it to `DEFAULT_HERO_DEFINITIONS`.
+This directory is the sole hero catalogue. Adding, changing, or removing a JSON
+file requires no separate registration step. Standalone Node/TSX programs read
+the directory from disk when they load the simulator config. The dashboard's
+Webpack build discovers the same files and bundles them; `next dev` also watches
+the directory, while production picks up changes on the next image build.
### `hero_generation` is opt-in build-time data
`hero_generation` is data, not an unconditional core-simulator effect. The core combat resolver copies the string into resolved output but does not add the associated stats by itself.
@@ -512,9 +516,9 @@
The engine accepts some values not currently used by a hero. This inventory distinguishes the data that exists in this directory from the wider native effect vocabulary documented above.
| Field | Values currently present |
| --- | --- |
-| `hero_generation` | `SR`, `S1`, `S1_natalia`, `S1_jeronimo`, `S2` through `S8` |
+| `hero_generation` | `SR`, `S1`, `S1_natalia`, `S1_jeronimo`, `S2` through `S10` |
| `troop_type` | `infantry`, `lancer`, `marksman` |
| `requirements[].type/value` | `engagement_type` with `rally` or `garrison`, always beginning at level 1 |
| `trigger.type` | `pre_battle`, `battle_start`, `turn`, `attack` |
| `trigger.source` | omitted, `infantry`, `lancer`, `marksman`, `self.any`, `self.all`, `enemy.any` |
Index: simulator/config/hero_definitions/Xura.json
===================================================================
--- simulator/config/hero_definitions/Xura.json prev run
+++ simulator/config/hero_definitions/Xura.json this run
@@ -0,0 +1,76 @@
+{
+ "name": "Xura",
+ "hero_generation": "S9",
+ "troop_type": "marksman",
+ "skills": {
+ "FungalFog": {
+ "description": "Xura releases an underground fungi that quickly multiplies to block enemy vision, reducing damage dealt to friendly troops by X%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "FungalFog/1": {
+ "type": "active.hero.damage.down",
+ "value": [4, 8, 12, 16, 20],
+ "units": { "applies_to": "enemy.any" }
+ }
+ }
+ },
+ "PiercingArrow": {
+ "description": "Xura's Marksmen deal X% additional damage every 2 strikes and make their target take Y% more damage for 1 turn.",
+ "trigger": {
+ "type": "attack",
+ "every": 2,
+ "source": "marksman"
+ },
+ "effects": {
+ "PiercingArrow/1": {
+ "type": "extra_skill_attack",
+ "value": [20, 40, 60, 80, 100],
+ "units": {
+ "applies_to": "trigger.source",
+ "applies_vs": "trigger.target"
+ },
+ "duration": { "attacks": { "count": 1 } },
+ "trigger_damage_jobs": [
+ { "source": "use.source", "target": "use.target" }
+ ]
+ },
+ "PiercingArrow/2": {
+ "type": "active.hero.damageTaken.up",
+ "value": [5, 10, 15, 20, 25],
+ "units": { "applies_to": "trigger.target" },
+ "duration": { "turns": { "count": 1 } },
+ "same_effect_stacking": "max"
+ }
+ }
+ },
+ "Unorthodoxy": {
+ "description": "Xura increases Marksmen damage dealt by X% while reducing their damage taken by Y%.",
+ "trigger": { "type": "battle_start" },
+ "effects": {
+ "Unorthodoxy/1": {
+ "type": "active.hero.damage.up",
+ "value": [3, 6, 9, 12, 15],
+ "units": { "applies_to": ["marksman"] }
+ },
+ "Unorthodoxy/2": {
+ "type": "active.hero.damageTaken.down",
+ "value": [2, 4, 6, 8, 10],
+ "units": { "applies_to": ["marksman"] }
+ }
+ }
+ },
+ "GaiacHymn": {
+ "description": "Widget bonus: Increase Attack by X% for all troops when defending.",
+ "trigger": { "type": "pre_battle" },
+ "requirements": [
+ { "level": 1, "type": "engagement_type", "value": "garrison" }
+ ],
+ "effects": {
+ "GaiacHymn/1": {
+ "type": "passive.attack.up",
+ "value": [5, 7.5, 10, 12.5, 15]
+ }
+ }
+ }
+ }
+}
Index: simulator/config/hero_generation_stats.json
===================================================================
--- simulator/config/hero_generation_stats.json prev run
+++ simulator/config/hero_generation_stats.json this run
@@ -61,6 +61,18 @@
"attack": 780.62,
"defense": 780.62,
"lethality": 193,
"health": 193
+ },
+ "S9": {
+ "attack": 940.75,
+ "defense": 940.75,
+ "lethality": 232,
+ "health": 232
+ },
+ "S10": {
+ "attack": 1110.88,
+ "defense": 1110.88,
+ "lethality": 277.5,
+ "health": 277.5
}
}
Index: simulator/src/config-default.ts
===================================================================
--- simulator/src/config-default.ts prev run
+++ simulator/src/config-default.ts this run
@@ -0,0 +1,3 @@
+// Standalone Node/TSX consumers resolve this module directly. Next's Webpack
+// build replaces it with config-webpack.ts so browser bundles never import fs.
+export { loadSimulatorConfig } from "./config-node";
Index: simulator/src/config-node.ts
===================================================================
--- simulator/src/config-node.ts prev run
+++ simulator/src/config-node.ts this run
@@ -1,10 +1,19 @@
import { readdirSync, readFileSync } from "node:fs";
import { join, relative, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
import { buildSimulatorConfig, type RawSimulatorConfig } from "./config";
import type { SimulatorConfig, SkillFile } from "./types";
+export const DEFAULT_CONFIG_DIR = fileURLToPath(
+ new URL("../config", import.meta.url),
+);
+
+export function loadSimulatorConfig(): SimulatorConfig {
+ return loadSimulatorConfigFromDir(DEFAULT_CONFIG_DIR);
+}
+
export function loadSimulatorConfigFromDir(configDir: string): SimulatorConfig {
const root = resolve(configDir);
const heroDir = join(root, "hero_definitions");
const heroDefinitions: Record<string, SkillFile> = {};
Index: simulator/src/config-webpack.ts
===================================================================
--- simulator/src/config-webpack.ts prev run
+++ simulator/src/config-webpack.ts this run
@@ -0,0 +1,39 @@
+import heroGenerationStatsJson from "../config/hero_generation_stats.json" with { type: "json" };
+import troopSkillsJson from "../config/troop_skills.json" with { type: "json" };
+
+import { buildSimulatorConfig } from "./config";
+import type { SimulatorConfig, SkillFile } from "./types";
+
+const heroDefinitionModules = import.meta.webpackContext(
+ "../config/hero_definitions",
+ {
+ recursive: false,
+ regExp: /\.json$/,
+ mode: "sync",
+ },
+);
+
+function moduleDefault(value: unknown): unknown {
+ if (!value || typeof value !== "object" || !("default" in value)) {
+ return value;
+ }
+ return (value as { default: unknown }).default;
+}
+
+function loadHeroDefinitions(): Record<string, SkillFile> {
+ return Object.fromEntries(
+ heroDefinitionModules.keys().map((modulePath) => {
+ const name = modulePath.replace(/^\.\//, "").replace(/\.json$/, "");
+ return [name, moduleDefault(heroDefinitionModules(modulePath)) as SkillFile];
+ }),
+ );
+}
+
+export function loadSimulatorConfig(): SimulatorConfig {
+ return buildSimulatorConfig({
+ heroGenerationStats:
+ heroGenerationStatsJson as SimulatorConfig["heroGenerationStats"],
+ troopSkills: troopSkillsJson as SkillFile,
+ heroDefinitions: loadHeroDefinitions(),
+ });
+}
Index: simulator/src/config.test.ts
===================================================================
--- simulator/src/config.test.ts prev run
+++ simulator/src/config.test.ts this run
@@ -4,10 +4,12 @@
import { test } from "node:test";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";
-import { loadSimulatorConfig } from "./config";
-import { loadSimulatorConfigFromDir } from "./config-node";
+import {
+ loadSimulatorConfig,
+ loadSimulatorConfigFromDir,
+} from "./config-node";
import type { SkillFile } from "./types";
test("Gwen Blastmaster uses a turn delay and a one-attack duration", () => {
const effect = loadSimulatorConfig().heroDefinitions.Gwen.skills.Blastmaster.effects["Blastmaster/1"];
Index: simulator/src/config.ts
===================================================================
--- simulator/src/config.ts prev run
+++ simulator/src/config.ts this run
@@ -1,40 +1,4 @@
-import heroGenerationStatsJson from "../config/hero_generation_stats.json" with { type: "json" };
-import troopSkillsJson from "../config/troop_skills.json" with { type: "json" };
-import Ahmose from "../config/hero_definitions/Ahmose.json" with { type: "json" };
-import Alonso from "../config/hero_definitions/Alonso.json" with { type: "json" };
-import Bahiti from "../config/hero_definitions/Bahiti.json" with { type: "json" };
-import Bradley from "../config/hero_definitions/Bradley.json" with { type: "json" };
-import Edith from "../config/hero_definitions/Edith.json" with { type: "json" };
-import Flint from "../config/hero_definitions/Flint.json" with { type: "json" };
-import Gatot from "../config/hero_definitions/Gatot.json" with { type: "json" };
-import Gordon from "../config/hero_definitions/Gordon.json" with { type: "json" };
-import Greg from "../config/hero_definitions/Greg.json" with { type: "json" };
-import Gwen from "../config/hero_definitions/Gwen.json" with { type: "json" };
-import Hector from "../config/hero_definitions/Hector.json" with { type: "json" };
-import Hendrik from "../config/hero_definitions/Hendrik.json" with { type: "json" };
-import Jasser from "../config/hero_definitions/Jasser.json" with { type: "json" };
-import Jeronimo from "../config/hero_definitions/Jeronimo.json" with { type: "json" };
-import Jessie from "../config/hero_definitions/Jessie.json" with { type: "json" };
-import Ling from "../config/hero_definitions/Ling.json" with { type: "json" };
-import Logan from "../config/hero_definitions/Logan.json" with { type: "json" };
-import Lumak from "../config/hero_definitions/Lumak.json" with { type: "json" };
-import Lynn from "../config/hero_definitions/Lynn.json" with { type: "json" };
-import Mia from "../config/hero_definitions/Mia.json" with { type: "json" };
-import Molly from "../config/hero_definitions/Molly.json" with { type: "json" };
-import Natalia from "../config/hero_definitions/Natalia.json" with { type: "json" };
-import Norah from "../config/hero_definitions/Norah.json" with { type: "json" };
-import Patrick from "../config/hero_definitions/Patrick.json" with { type: "json" };
-import Philly from "../config/hero_definitions/Philly.json" with { type: "json" };
-import Reina from "../config/hero_definitions/Reina.json" with { type: "json" };
-import Renee from "../config/hero_definitions/Renee.json" with { type: "json" };
-import SeoYoon from "../config/hero_definitions/Seo-yoon.json" with { type: "json" };
-import Sergey from "../config/hero_definitions/Sergey.json" with { type: "json" };
-import Sonya from "../config/hero_definitions/Sonya.json" with { type: "json" };
-import Wayne from "../config/hero_definitions/Wayne.json" with { type: "json" };
-import WuMing from "../config/hero_definitions/WuMing.json" with { type: "json" };
-import Zinman from "../config/hero_definitions/Zinman.json" with { type: "json" };
-
import { UNIT_TYPES } from "./types";
import type { ConfigDiagnostics, EffectIntentDefinition, SimulatorConfig, SkillFile, TriggerDamageJobDefinition } from "./types";
import {
DYNAMIC_EFFECT_BUCKETS,
@@ -54,59 +18,15 @@
"no_attack",
"attack_order"
]);
-const DEFAULT_HERO_DEFINITIONS = {
- Ahmose,
- Alonso,
- Bahiti,
- Bradley,
- Edith,
- Flint,
- Gatot,
- Gordon,
- Greg,
- Gwen,
- Hector,
- Hendrik,
- Jasser,
- Jeronimo,
- Jessie,
- Ling,
- Logan,
- Lumak,
- Lynn,
- Mia,
- Molly,
- Natalia,
- Norah,
- Patrick,
- Philly,
- Reina,
- Renee,
- "Seo-yoon": SeoYoon,
- Sergey,
- Sonya,
- Wayne,
- WuMing,
- Zinman
-} as unknown as Record<string, SkillFile>;
-
export interface RawSimulatorConfig {
heroGenerationStats: SimulatorConfig["heroGenerationStats"];
troopSkills: SkillFile;
heroDefinitions: Record<string, SkillFile>;
fileLabel?: (kind: "hero_generation_stats" | "troop_skills" | "hero_definition", key?: string) => string;
}
-export function loadSimulatorConfig(): SimulatorConfig {
- return buildSimulatorConfig({
- heroGenerationStats: heroGenerationStatsJson as SimulatorConfig["heroGenerationStats"],
- troopSkills: troopSkillsJson as SkillFile,
- heroDefinitions: DEFAULT_HERO_DEFINITIONS
- });
-}
-
export function buildSimulatorConfig(raw: RawSimulatorConfig): SimulatorConfig {
const diagnostics: ConfigDiagnostics = {
legacyFields: [],
effectTypes: {},
Index: simulator/src/gen8Heroes.test.ts
===================================================================
--- simulator/src/gen8Heroes.test.ts prev run
+++ simulator/src/gen8Heroes.test.ts this run
@@ -1,408 +0,0 @@
-import assert from "node:assert/strict";
-import { test } from "node:test";
-
-import { loadSimulatorConfig } from "./config";
-import { resolveFighter } from "./fighterResolution";
-import { simulateBattles } from "./simulator";
-import type { BattleInput } from "./types";
-
-test("Gen8 heroes are registered with the shared S8 expedition stats", () => {
- const config = loadSimulatorConfig();
-
- assert.deepEqual(config.heroGenerationStats.S8, {
- attack: 780.62,
- defense: 780.62,
- lethality: 193,
- health: 193
- });
- assert.deepEqual(
- ["Gatot", "Sonya", "Hendrik"].map((name) => [name, config.heroDefinitions[name]?.hero_generation]),
- [
- ["Gatot", "S8"],
- ["Sonya", "S8"],
- ["Hendrik", "S8"]
- ]
- );
-});
-
-test("Gen8 widget skills are fourth, passive, and engagement-gated", () => {
- const config = loadSimulatorConfig();
- const expectations = [
- ["Gatot", "IndestructibleCity", "garrison", "passive.defense.up"],
- ["Sonya", "VortexTurret", "garrison", "passive.lethality.up"],
- ["Hendrik", "AbyssalBlessing", "rally", "passive.attack.up"]
- ] as const;
-
- for (const [heroName, skillName, engagement, effectType] of expectations) {
- const definition = config.heroDefinitions[heroName];
- const skillEntries = Object.entries(definition.skills);
- assert.equal(skillEntries.length, 4);
- assert.equal(skillEntries[3][0], skillName);
- assert.equal(Object.values(skillEntries[3][1].effects)[0].type, effectType);
- assert.deepEqual(skillEntries[3][1].requirements, [
- { level: 1, type: "engagement_type", value: engagement }
- ]);
-
- const matchingSide = engagement === "rally" ? "attacker" : "defender";
- const gatedOutSide = engagement === "rally" ? "defender" : "attacker";
- const matching = resolveFighter(
- { troops: { infantry_t1: 1 }, heroes: { [heroName]: { skill_4: 5 } } },
- matchingSide,
- config,
- "rally"
- );
- const gatedOut = resolveFighter(
- { troops: { infantry_t1: 1 }, heroes: { [heroName]: { skill_4: 5 } } },
- gatedOutSide,
- config,
- "rally"
- );
- assert.deepEqual(matching.heroes[0]?.skillIds, [skillName]);
- assert.deepEqual(gatedOut.heroes[0]?.skillIds, []);
- }
-});
-
-test("Gen8 combat skill values and active effect buckets match their definitions", () => {
- const config = loadSimulatorConfig();
- const gatot = config.heroDefinitions.Gatot.skills;
- const sonya = config.heroDefinitions.Sonya.skills;
- const hendrik = config.heroDefinitions.Hendrik.skills;
-
- assert.deepEqual(gatot.GoldenGuard.effects["GoldenGuard/1"].value, [6, 12, 18, 24, 30]);
- assert.equal(gatot.GoldenGuard.effects["GoldenGuard/1"].type, "active.hero.defense.up");
- assert.deepEqual(gatot.KingsBestowal.effects["KingsBestowal/1"].value, [6, 12, 18, 24, 30]);
- assert.deepEqual(gatot.KingsBestowal.effects["KingsBestowal/1"], {
- type: "active.hero.shield",
- value: [6, 12, 18, 24, 30],
- value_formula: { type: "percent_of", source: "trigger.source_attack" },
- units: { applies_to: "trigger.source", applies_vs: "enemy.any" },
- duration: { turns: { delay: 1, count: 1 } },
- same_effect_stacking: "max"
- });
- assert.deepEqual(gatot.RoyalLegion.effects["RoyalLegion/1"].value, [5, 10, 15, 20, 25]);
- assert.equal(gatot.RoyalLegion.effects["RoyalLegion/1"].type, "active.hero.attack.down");
-
- assert.deepEqual(sonya.TreasureHunter.effects["TreasureHunter/1"].value, [4, 8, 12, 16, 20]);
- assert.equal(sonya.BountyTemptation.effects["BountyTemptation/1"].type, "extra_skill_attack");
- assert.deepEqual(sonya.BountyTemptation.effects["BountyTemptation/1"].value, [15, 30, 45, 60, 75]);
- assert.deepEqual(sonya.BountyTemptation.effects["BountyTemptation/1"].trigger_damage_jobs, [
- { source: "use.source", target: "use.target" }
- ]);
- assert.equal(sonya.BountyTemptation.effects["BountyTemptation/2"].type, "active.hero.attack.up");
- assert.deepEqual(sonya.TorrentialImpact.effects["TorrentialImpact/1"].value, [50, 100, 150, 200, 250]);
- assert.equal(sonya.TorrentialImpact.effects["TorrentialImpact/1"].type, "extra_skill_attack");
- assert.deepEqual(sonya.TorrentialImpact.effects["TorrentialImpact/1"].trigger_damage_jobs, [
- { source: "use.source", target: "use.target" }
- ]);
- assert.equal(sonya.TorrentialImpact.effects["TorrentialImpact/2"].type, "no_attack");
-
- assert.deepEqual(hendrik.WormsRavage.effects["WormsRavage/1"].value, [5, 10, 15, 20, 25]);
- assert.equal(hendrik.WormsRavage.effects["WormsRavage/1"].type, "active.hero.defense.down");
- assert.deepEqual(hendrik.ArmorOfBarnacles.effects["ArmorOfBarnacles/1"].value, [6, 12, 18, 24, 30]);
- assert.equal(hendrik.ArmorOfBarnacles.effects["ArmorOfBarnacles/1"].type, "active.hero.defense.up");
- assert.deepEqual(hendrik.DragonsHeir.effects["DragonsHeir/1"].value, [8, 16, 24, 32, 40]);
- assert.equal(hendrik.DragonsHeir.effects["DragonsHeir/1"].type, "extra_skill_attack");
- assert.deepEqual(hendrik.DragonsHeir.effects["DragonsHeir/1"].trigger_damage_jobs, [
- { source: "use.source", target: "enemy.living" }
- ]);
-});
-
-test("Gatot converts source formation Attack into next-turn shield protection", () => {
- const config = loadSimulatorConfig();
- const input = gatotInfantryBattle(5000, 1000, 2);
- const result = simulateBattles(input, config, { mode: "trace" })[0]!;
- const roundTwoLeftAttack = result.attacks.find(
- (attack) => attack.round === 2 && attack.kind === "normal" && attack.dealerSide === "attacker"
- );
- const rightShield = roundTwoLeftAttack?.appliedEffects?.find((effect) => "kind" in effect && effect.kind === "shield");
- const expectedRightShield = Math.sqrt(1000) * (243 * (1 + 326.1 / 100)) / (730 * (1 + 18.2 / 100)) * 0.12;
-
- assert.ok(rightShield && "value" in rightShield);
- assert.ok(Math.abs(rightShield.value - expectedRightShield) < 1e-12);
- const unroundedDamage = Math.max(0, (roundTwoLeftAttack?.trace?.damageBeforeOffsets ?? 0) - expectedRightShield);
- assert.equal(roundTwoLeftAttack?.kills, Math.ceil(unroundedDamage * 1000) / 1000);
-});
-
-test("Gatot caps the shield formation count at the smaller initial army", () => {
- const config = loadSimulatorConfig();
- const cases = [
- { attacker: 450, defender: 200, attackerRemaining: 173, defenderRemaining: 200 },
- { attacker: 2500, defender: 500, attackerRemaining: 974, defenderRemaining: 498 }
- ] as const;
-
- for (const expected of cases) {
- const result = simulateBattles(
- gatotReciprocalMagnitudeBattle(expected.attacker, expected.defender),
- config,
- { mode: "trace" }
- )[0]!;
- const roundTwoDefenderAttack = result.attacks.find(
- (attack) =>
- attack.round === 2 &&
- attack.kind === "normal" &&
- attack.dealerSide === "defender"
- );
- const shield = roundTwoDefenderAttack?.appliedEffects?.find(
- (effect) => "kind" in effect && effect.kind === "shield"
- );
- const expectedShield =
- Math.sqrt(expected.defender) *
- (243 * (1 + 472.7 / 100)) /
- (730 * (1 + 164 / 100)) *
- 0.06;
-
- assert.ok(shield && "value" in shield);
- assert.ok(Math.abs(shield.value - expectedShield) < 1e-12);
- assert.deepEqual(
- {
- rounds: result.rounds,
- attackerRemaining: result.remaining.attacker.infantry,
- defenderRemaining: result.remaining.defender.infantry
- },
- {
- rounds: 1500,
- attackerRemaining: expected.attackerRemaining,
- defenderRemaining: expected.defenderRemaining
- }
- );
- }
-});
-
-test("Gatot shields normal and genuine extra-skill attacks", () => {
- const config = loadSimulatorConfig();
- const input: BattleInput = {
- maxRounds: 4,
- seed: "gatot-all-attack-shield",
- attacker: {
- troops: { infantry_t6: 200 },
- stats: {
- infantry: { attack: 530.8, defense: 527.5, lethality: 218.4, health: 219.6 }
- },
- heroes: { Gatot: { skill_1: 1, skill_2: 3, skill_3: 1 } }
- },
- defender: {
- troops: { infantry_t6: 450 },
- stats: {
- infantry: { attack: 472.6, defense: 471.9, lethality: 147.5, health: 160.9 }
- },
- heroes: {
- Gatot: { skill_1: 1, skill_2: 1, skill_3: 1 },
- Wayne: { skill_1: 3, skill_2: 3, skill_3: 3 }
- }
- }
- };
-
- const result = simulateBattles(input, config, { mode: "trace" })[0]!;
- const roundFourNormal = result.attacks.find(
- (attack) =>
- attack.round === 4 &&
- attack.kind === "normal" &&
- attack.dealerSide === "defender" &&
- attack.dealerUnit === "infantry"
- );
- const thunderStrike = result.attacks.find(
- (attack) =>
- attack.round === 4 &&
- attack.kind === "skill" &&
- attack.dealerSide === "defender" &&
- attack.sourceEffectId === "ThunderStrike/1"
- );
-
- assert.ok(roundFourNormal?.appliedEffects?.some((effect) => "kind" in effect && effect.kind === "shield"));
- assert.ok(thunderStrike?.appliedEffects?.some((effect) => "kind" in effect && effect.kind === "shield"));
- assert.equal(roundFourNormal?.kills, 0);
- assert.equal(thunderStrike?.kills, 0);
-});
-
-test("Gatot source-Attack shields reproduce the observed army-size threshold", () => {
- const config = loadSimulatorConfig();
- const expectations = [
- { left: 4900, right: 1000, winner: "draw", rounds: 1500, leftLosses: 3, rightLosses: 4 },
- { left: 5100, right: 1000, winner: "attacker", rounds: 1378, leftLosses: 3, rightLosses: 1000 },
- { left: 10000, right: 2000, winner: "attacker", rounds: 452, leftLosses: 14, rightLosses: 2000 },
- { left: 20000, right: 4000, winner: "attacker", rounds: 342, leftLosses: 541, rightLosses: 4000 }
- ] as const;
-
- for (const expected of expectations) {
- const result = simulateBattles(gatotInfantryBattle(expected.left, expected.right), config, { mode: "fast" })[0]!;
- assert.deepEqual(
- {
- winner: result.winner,
- rounds: result.rounds,
- leftLosses: expected.left - result.remaining.attacker.infantry,
- rightLosses: expected.right - result.remaining.defender.infantry
- },
- {
- winner: expected.winner,
- rounds: expected.rounds,
- leftLosses: expected.leftLosses,
- rightLosses: expected.rightLosses
- }
- );
- }
-});
-
-test("Gatot conserves one complete turn shield across enemy formations", () => {
- const input: BattleInput = {
- maxRounds: 1500,
- seed: "10024f8d-b3e3-423a-832c-5176cfbbbd7d",
- attacker: {
- troops: { infantry_t6: 5000 },
- stats: { infantry: { attack: 295.3, defense: 293.3, lethality: 10, health: 10 } },
- heroes: { Gatot: { skill_1: 1, skill_2: 3, skill_3: 3 } }
- },
- defender: {
- troops: { infantry_t6: 1000, lancer_t6: 10 },
- stats: {
- infantry: { attack: 326.1, defense: 330.1, lethality: 18.2, health: 18.2 },
- lancer: { attack: 284.8, defense: 288.8, lethality: 18.2, health: 18.2 }
- },
- heroes: {
- Gatot: { skill_1: 2, skill_2: 2, skill_3: 2 },
- Gordon: { skill_1: 3, skill_2: 3, skill_3: 3 }
- }
- }
- };
-
- const result = simulateBattles(input, loadSimulatorConfig(), { mode: "fast" })[0]!;
-
- assert.deepEqual(
- { winner: result.winner, rounds: result.rounds, leftInfantry: result.remaining.attacker.infantry },
- { winner: "attacker", rounds: 1036, leftInfantry: 4996 }
- );
- assert.deepEqual(result.remaining.defender, { infantry: 0, lancer: 0, marksman: 0 });
-});
-
-test("Gatot turn-shield pooling reproduces the mixed-formation family", () => {
- const config = loadSimulatorConfig();
- const expectations = [
- {
- infantry: 1000,
- lancer: 1000,
- marksman: 0,
- winner: "defender",
- rounds: 266,
- attackerSurvivors: 0,
- defenderSurvivors: 4972
- },
- {
- infantry: 1000,
- lancer: 0,
- marksman: 1000,
- winner: "defender",
- rounds: 278,
- attackerSurvivors: 0,
- defenderSurvivors: 4487
- },
- {
- infantry: 2000,
- lancer: 1000,
- marksman: 0,
- winner: "defender",
- rounds: 546,
- attackerSurvivors: 0,
- defenderSurvivors: 4332
- },
- {
- infantry: 1000,
- lancer: 1000,
- marksman: 1000,
- winner: "defender",
- rounds: 239,
- attackerSurvivors: 0,
- defenderSurvivors: 3052
- },
- {
- infantry: 2000,
- lancer: 0,
- marksman: 1000,
- winner: "attacker",
- rounds: 895,
- attackerSurvivors: 1337,
- defenderSurvivors: 0
- }
- ] as const;
-
- for (const expected of expectations) {
- const result = simulateBattles(
- gatotMixedBattle(expected.infantry, expected.lancer, expected.marksman),
- config,
- { mode: "fast" }
- )[0]!;
- const attackerSurvivors = Object.values(result.remaining.attacker).reduce((sum, count) => sum + count, 0);
- const defenderSurvivors = Object.values(result.remaining.defender).reduce((sum, count) => sum + count, 0);
- assert.deepEqual(
- {
- winner: result.winner,
- rounds: result.rounds,
- attackerSurvivors,
- defenderSurvivors
- },
- {
- winner: expected.winner,
- rounds: expected.rounds,
- attackerSurvivors: expected.attackerSurvivors,
- defenderSurvivors: expected.defenderSurvivors
- }
- );
- }
-});
-
-function gatotInfantryBattle(left: number, right: number, maxRounds = 1500): BattleInput {
- return {
- maxRounds,
- seed: "gatot-source-attack-shield",
- attacker: {
- troops: { infantry_t6: left },
- stats: { infantry: { attack: 285.3, defense: 283.3, lethality: 0, health: 0 } },
- heroes: { Gatot: { skill_1: 1, skill_2: 2, skill_3: 3 } }
- },
- defender: {
- troops: { infantry_t6: right },
- stats: { infantry: { attack: 326.1, defense: 330.1, lethality: 18.2, health: 18.2 } },
- heroes: { Gatot: { skill_1: 2, skill_2: 2, skill_3: 2 } }
- }
- };
-}
-
-function gatotReciprocalMagnitudeBattle(attacker: number, defender: number): BattleInput {
- return {
- maxRounds: 1500,
- seed: `gatot-reciprocal-magnitude-${attacker}-${defender}`,
- attacker: {
- troops: { infantry_t6: attacker },
- stats: {
- infantry: { attack: 472.7, defense: 472, lethality: 150.6, health: 164 }
- },
- heroes: { Gatot: { skill_1: 1, skill_2: 1, skill_3: 1 } }
- },
- defender: {
- troops: { infantry_t6: defender },
- stats: {
- infantry: { attack: 530.8, defense: 527.5, lethality: 218.4, health: 219.6 }
- },
- heroes: { Gatot: { skill_1: 1, skill_2: 3, skill_3: 1 } }
- }
- };
-}
-
-function gatotMixedBattle(infantry: number, lancer: number, marksman: number): BattleInput {
- return {
- maxRounds: 1500,
- seed: `gatot-mixed-${infantry}-${lancer}-${marksman}`,
- attacker: {
- troops: { infantry_t6: infantry, lancer_t6: lancer, marksman_t6: marksman },
- stats: {
- infantry: { attack: 326.1, defense: 330.1, lethality: 18.2, health: 18.2 },
- lancer: { attack: 78.1, defense: 82.1, lethality: 18.2, health: 18.2 },
- marksman: { attack: 78.1, defense: 82.1, lethality: 18.2, health: 18.2 }
- },
- heroes: { Gatot: { skill_1: 2, skill_2: 2, skill_3: 2 } }
- },
- defender: {
- troops: { infantry_t6: 5000 },
- stats: { infantry: { attack: 295.3, defense: 293.3, lethality: 10, health: 10 } },
- heroes: { Gatot: { skill_1: 1, skill_2: 3, skill_3: 3 } }
- }
- };
-}
Index: simulator/src/index.ts
===================================================================
--- simulator/src/index.ts prev run
+++ simulator/src/index.ts this run
@@ -1,9 +0,0 @@
-export { buildSimulatorConfig, loadSimulatorConfig } from "./config";
-export type { RawSimulatorConfig } from "./config";
-export { createTroopStatsRecord, fireCrystalMultiplier, generateTroopStats, generateTroopStatsCatalogue } from "./troopStats";
-export { prepareBattle } from "./prepare";
-export type { CompiledBattle } from "./prepare";
-export { runPrepared, simulateBattles } from "./simulator";
-export { BattleInputBuilder } from "./battleInputBuilder";
-export { applyHeroGenerationStats } from "./fighterResolution";
-export type * from "./types";
Index: simulator/src/simulator.test.ts
===================================================================
--- simulator/src/simulator.test.ts prev run
+++ simulator/src/simulator.test.ts this run
@@ -3,9 +3,9 @@
import { test } from "node:test";
import { fileURLToPath } from "node:url";
import { BattleInputBuilder } from "./battleInputBuilder";
-import { loadSimulatorConfig } from "./config";
+import { loadSimulatorConfig } from "./config-node";
import { createSeededRng, chancePasses, compiledTriggerForSkill } from "./effects";
import { applyHeroGenerationStats, resolveFighter } from "./fighterResolution";
import { prepareBattle, runPrepared, simulateBattles, simulateBearBattle, signedRemainingScore } from "./simulator";
import { BEAR_TROOP_ID, createTroopStatsRecord, generateTroopStatsCatalogue } from "./troopStats";
Index: simulator/src/tooling/exportGatotTestcases.ts
===================================================================
--- simulator/src/tooling/exportGatotTestcases.ts prev run
+++ simulator/src/tooling/exportGatotTestcases.ts this run
@@ -1,9 +1,9 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { pathToFileURL } from "node:url";
-import { loadSimulatorConfig } from "../config";
+import { loadSimulatorConfig } from "../config-node";
import type { BattleInput, SimulatorConfig } from "../types";
import { GATOT_GAME_OBSERVATIONS, type GatotGameObservation } from "./gatotEvidence";
export interface GatotTestcaseArtifact {
Index: simulator/src/tooling/gatotEvidence.test.ts
===================================================================
--- simulator/src/tooling/gatotEvidence.test.ts prev run
+++ simulator/src/tooling/gatotEvidence.test.ts this run
@@ -1,8 +1,8 @@
import assert from "node:assert/strict";
import { test } from "node:test";
-import { loadSimulatorConfig } from "../config";
+import { loadSimulatorConfig } from "../config-node";
import {
GATOT_GAME_OBSERVATIONS,
renderGatotEvidenceMarkdown,
runGatotEvidence,
Index: simulator/src/tooling/gatotEvidence.ts
===================================================================
--- simulator/src/tooling/gatotEvidence.ts prev run
+++ simulator/src/tooling/gatotEvidence.ts this run
@@ -1,8 +1,8 @@
import { pathToFileURL } from "node:url";
import { BattleInputBuilder } from "../battleInputBuilder";
-import { loadSimulatorConfig } from "../config";
+import { loadSimulatorConfig } from "../config-node";
import { simulateBattles } from "../simulator";
import type {
BattleInput,
BattleResult,
Index: simulator/src/tooling/testcases.test.ts
===================================================================
--- simulator/src/tooling/testcases.test.ts prev run
+++ simulator/src/tooling/testcases.test.ts this run
@@ -3,9 +3,9 @@
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import { test } from "node:test";
-import { loadSimulatorConfig } from "../config";
+import { loadSimulatorConfig } from "../config-node";
import { loadCalibrationComparison, readCalibrationCase, testcaseFileLookupVariants } from "./calibration";
import { applyBenjaminiHochberg, compareOutcomeDistribution, type ParityComparisonMetrics } from "./parityMetrics";
import { adaptTestcaseEntry, applyComparisonQValues, assignDetailArtifactPaths, battleScoreDelta, buildSummaryForOutput, deterministicRoundTolerancePct, discoverTestcaseFiles, runTestcases, type TestcaseSummaryEntry } from "./testcases";
Index: simulator/src/webpack-context.d.ts
===================================================================
--- simulator/src/webpack-context.d.ts prev run
+++ simulator/src/webpack-context.d.ts this run
@@ -0,0 +1,19 @@
+interface WebpackContext {
+ (request: string): unknown;
+ keys(): string[];
+ resolve(request: string): string | number;
+ id: string | number;
+}
+
+interface ImportMeta {
+ webpackContext(
+ directory: string,
+ options?: {
+ recursive?: boolean;
+ regExp?: RegExp;
+ include?: RegExp;
+ exclude?: RegExp;
+ mode?: "sync" | "eager" | "weak" | "lazy" | "lazy-once";
+ },
+ ): WebpackContext;
+}