diff --git a/SVSim.Bootstrap/Data/prod-captures/practice-info-2026-05-23.json b/SVSim.Bootstrap/Data/prod-captures/practice-info-2026-05-23.json
deleted file mode 100644
index 142f338..0000000
--- a/SVSim.Bootstrap/Data/prod-captures/practice-info-2026-05-23.json
+++ /dev/null
@@ -1 +0,0 @@
-{"data_headers":{"sid":"2aee92feffc2d16d494e5b45926a3f991779588133","short_udid":411054851,"viewer_id":906243102,"servertime":1779588133,"result_code":1},"data":[{"null":1,"practice_id":"1106","text_id":"Practice_1106","class_id":"1","chara_id":"1","degree_id":"-1","ai_deck_level":"106","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1001","text_id":"Practice_1001","class_id":"1","chara_id":"1","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"10","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1002","text_id":"Practice_1002","class_id":"1","chara_id":"1","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1003","text_id":"Practice_1003","class_id":"1","chara_id":"1","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1004","text_id":"Practice_1004","class_id":"1","chara_id":"1","degree_id":"400003","ai_deck_level":"5","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1005","text_id":"Practice_1005","class_id":"1","chara_id":"1","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1006","text_id":"Practice_1006","class_id":"1","chara_id":"1","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1007","text_id":"Practice_1007","class_id":"1","chara_id":"1","degree_id":"400004","ai_deck_level":"7","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1101","text_id":"Practice_1101","class_id":"1","chara_id":"1","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1102","text_id":"Practice_1102","class_id":"1","chara_id":"1","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1103","text_id":"Practice_1103","class_id":"1","chara_id":"1","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1104","text_id":"Practice_1104","class_id":"1","chara_id":"1","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"1105","text_id":"Practice_1105","class_id":"1","chara_id":"1","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"1","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2106","text_id":"Practice_2106","class_id":"2","chara_id":"2","degree_id":"-1","ai_deck_level":"106","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2001","text_id":"Practice_2001","class_id":"2","chara_id":"2","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"10","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2002","text_id":"Practice_2002","class_id":"2","chara_id":"2","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2003","text_id":"Practice_2003","class_id":"2","chara_id":"2","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2004","text_id":"Practice_2004","class_id":"2","chara_id":"2","degree_id":"400003","ai_deck_level":"5","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2005","text_id":"Practice_2005","class_id":"2","chara_id":"2","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2006","text_id":"Practice_2006","class_id":"2","chara_id":"2","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2007","text_id":"Practice_2007","class_id":"2","chara_id":"2","degree_id":"400004","ai_deck_level":"7","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2101","text_id":"Practice_2101","class_id":"2","chara_id":"2","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2102","text_id":"Practice_2102","class_id":"2","chara_id":"2","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2103","text_id":"Practice_2103","class_id":"2","chara_id":"2","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2104","text_id":"Practice_2104","class_id":"2","chara_id":"2","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"2105","text_id":"Practice_2105","class_id":"2","chara_id":"2","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"4","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3106","text_id":"Practice_3106","class_id":"3","chara_id":"3","degree_id":"-1","ai_deck_level":"106","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3001","text_id":"Practice_3001","class_id":"3","chara_id":"3","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"10","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3002","text_id":"Practice_3002","class_id":"3","chara_id":"3","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3003","text_id":"Practice_3003","class_id":"3","chara_id":"3","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3004","text_id":"Practice_3004","class_id":"3","chara_id":"3","degree_id":"400003","ai_deck_level":"5","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3005","text_id":"Practice_3005","class_id":"3","chara_id":"3","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3006","text_id":"Practice_3006","class_id":"3","chara_id":"3","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3007","text_id":"Practice_3007","class_id":"3","chara_id":"3","degree_id":"400004","ai_deck_level":"7","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3101","text_id":"Practice_3101","class_id":"3","chara_id":"3","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3102","text_id":"Practice_3102","class_id":"3","chara_id":"3","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3103","text_id":"Practice_3103","class_id":"3","chara_id":"3","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3104","text_id":"Practice_3104","class_id":"3","chara_id":"3","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"3105","text_id":"Practice_3105","class_id":"3","chara_id":"3","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"7","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4107","text_id":"Practice_4107","class_id":"4","chara_id":"4","degree_id":"-1","ai_deck_level":"107","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4001","text_id":"Practice_4001","class_id":"4","chara_id":"4","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"10","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4002","text_id":"Practice_4002","class_id":"4","chara_id":"4","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4003","text_id":"Practice_4003","class_id":"4","chara_id":"4","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4004","text_id":"Practice_4004","class_id":"4","chara_id":"4","degree_id":"400003","ai_deck_level":"5","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4005","text_id":"Practice_4005","class_id":"4","chara_id":"4","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4006","text_id":"Practice_4006","class_id":"4","chara_id":"4","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4007","text_id":"Practice_4007","class_id":"4","chara_id":"4","degree_id":"400004","ai_deck_level":"7","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4101","text_id":"Practice_4101","class_id":"4","chara_id":"4","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4102","text_id":"Practice_4102","class_id":"4","chara_id":"4","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4103","text_id":"Practice_4103","class_id":"4","chara_id":"4","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4104","text_id":"Practice_4104","class_id":"4","chara_id":"4","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4105","text_id":"Practice_4105","class_id":"4","chara_id":"4","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"4106","text_id":"Practice_4106","class_id":"4","chara_id":"4","degree_id":"-1","ai_deck_level":"106","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"3","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5105","text_id":"Practice_5105","class_id":"5","chara_id":"5","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5001","text_id":"Practice_5001","class_id":"5","chara_id":"5","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"10","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5002","text_id":"Practice_5002","class_id":"5","chara_id":"5","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5003","text_id":"Practice_5003","class_id":"5","chara_id":"5","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5004","text_id":"Practice_5004","class_id":"5","chara_id":"5","degree_id":"400003","ai_deck_level":"5","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5005","text_id":"Practice_5005","class_id":"5","chara_id":"5","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5006","text_id":"Practice_5006","class_id":"5","chara_id":"5","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5007","text_id":"Practice_5007","class_id":"5","chara_id":"5","degree_id":"400004","ai_deck_level":"7","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5101","text_id":"Practice_5101","class_id":"5","chara_id":"5","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5102","text_id":"Practice_5102","class_id":"5","chara_id":"5","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5103","text_id":"Practice_5103","class_id":"5","chara_id":"5","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"5104","text_id":"Practice_5104","class_id":"5","chara_id":"5","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"6","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6109","text_id":"Practice_6109","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"109","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6001","text_id":"Practice_6001","class_id":"6","chara_id":"6","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"10","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6002","text_id":"Practice_6002","class_id":"6","chara_id":"6","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6003","text_id":"Practice_6003","class_id":"6","chara_id":"6","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6004","text_id":"Practice_6004","class_id":"6","chara_id":"6","degree_id":"400003","ai_deck_level":"5","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6005","text_id":"Practice_6005","class_id":"6","chara_id":"6","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6006","text_id":"Practice_6006","class_id":"6","chara_id":"6","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6007","text_id":"Practice_6007","class_id":"6","chara_id":"6","degree_id":"400004","ai_deck_level":"7","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6101","text_id":"Practice_6101","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6102","text_id":"Practice_6102","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6103","text_id":"Practice_6103","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6104","text_id":"Practice_6104","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6105","text_id":"Practice_6105","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6106","text_id":"Practice_6106","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"106","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6107","text_id":"Practice_6107","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"107","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"6108","text_id":"Practice_6108","class_id":"6","chara_id":"6","degree_id":"-1","ai_deck_level":"108","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"2","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7105","text_id":"Practice_7105","class_id":"7","chara_id":"7","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7001","text_id":"Practice_7001","class_id":"7","chara_id":"7","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"10","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7002","text_id":"Practice_7002","class_id":"7","chara_id":"7","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7003","text_id":"Practice_7003","class_id":"7","chara_id":"7","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7004","text_id":"Practice_7004","class_id":"7","chara_id":"7","degree_id":"400003","ai_deck_level":"5","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7005","text_id":"Practice_7005","class_id":"7","chara_id":"7","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7006","text_id":"Practice_7006","class_id":"7","chara_id":"7","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7007","text_id":"Practice_7007","class_id":"7","chara_id":"7","degree_id":"400004","ai_deck_level":"7","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7101","text_id":"Practice_7101","class_id":"7","chara_id":"7","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7102","text_id":"Practice_7102","class_id":"7","chara_id":"7","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7103","text_id":"Practice_7103","class_id":"7","chara_id":"7","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"7104","text_id":"Practice_7104","class_id":"7","chara_id":"7","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"5","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8107","text_id":"Practice_8107","class_id":"8","chara_id":"8","degree_id":"-1","ai_deck_level":"107","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8001","text_id":"Practice_8001","class_id":"8","chara_id":"8","degree_id":"400001","ai_deck_level":"0","ai_logic_level":"0","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8002","text_id":"Practice_8002","class_id":"8","chara_id":"8","degree_id":"400002","ai_deck_level":"2","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8003","text_id":"Practice_8003","class_id":"8","chara_id":"8","degree_id":"400003","ai_deck_level":"3","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8005","text_id":"Practice_8005","class_id":"8","chara_id":"8","degree_id":"400004","ai_deck_level":"4","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8006","text_id":"Practice_8006","class_id":"8","chara_id":"8","degree_id":"400004","ai_deck_level":"6","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8101","text_id":"Practice_8101","class_id":"8","chara_id":"8","degree_id":"-1","ai_deck_level":"101","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8102","text_id":"Practice_8102","class_id":"8","chara_id":"8","degree_id":"-1","ai_deck_level":"102","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8103","text_id":"Practice_8103","class_id":"8","chara_id":"8","degree_id":"-1","ai_deck_level":"103","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8104","text_id":"Practice_8104","class_id":"8","chara_id":"8","degree_id":"-1","ai_deck_level":"104","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8105","text_id":"Practice_8105","class_id":"8","chara_id":"8","degree_id":"-1","ai_deck_level":"105","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false},{"null":1,"practice_id":"8106","text_id":"Practice_8106","class_id":"8","chara_id":"8","degree_id":"-1","ai_deck_level":"106","ai_logic_level":"2","ai_max_life":"20","battle3dfield_id":"18","is_campaign_practice":false,"is_maintenance":false}]}
diff --git a/SVSim.Bootstrap/Data/seeds/practice-opponents.json b/SVSim.Bootstrap/Data/seeds/practice-opponents.json
new file mode 100644
index 0000000..158f146
--- /dev/null
+++ b/SVSim.Bootstrap/Data/seeds/practice-opponents.json
@@ -0,0 +1,1367 @@
+[
+ {
+ "practice_id": 1106,
+ "text_id": "Practice_1106",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": -1,
+ "ai_deck_level": 106,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1001,
+ "text_id": "Practice_1001",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 10,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1002,
+ "text_id": "Practice_1002",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1003,
+ "text_id": "Practice_1003",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1004,
+ "text_id": "Practice_1004",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": 400003,
+ "ai_deck_level": 5,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1005,
+ "text_id": "Practice_1005",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1006,
+ "text_id": "Practice_1006",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1007,
+ "text_id": "Practice_1007",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": 400004,
+ "ai_deck_level": 7,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1101,
+ "text_id": "Practice_1101",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1102,
+ "text_id": "Practice_1102",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1103,
+ "text_id": "Practice_1103",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1104,
+ "text_id": "Practice_1104",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 1105,
+ "text_id": "Practice_1105",
+ "class_id": 1,
+ "chara_id": 1,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "1",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2106,
+ "text_id": "Practice_2106",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": -1,
+ "ai_deck_level": 106,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2001,
+ "text_id": "Practice_2001",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 10,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2002,
+ "text_id": "Practice_2002",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2003,
+ "text_id": "Practice_2003",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2004,
+ "text_id": "Practice_2004",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": 400003,
+ "ai_deck_level": 5,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2005,
+ "text_id": "Practice_2005",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2006,
+ "text_id": "Practice_2006",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2007,
+ "text_id": "Practice_2007",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": 400004,
+ "ai_deck_level": 7,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2101,
+ "text_id": "Practice_2101",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2102,
+ "text_id": "Practice_2102",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2103,
+ "text_id": "Practice_2103",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2104,
+ "text_id": "Practice_2104",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 2105,
+ "text_id": "Practice_2105",
+ "class_id": 2,
+ "chara_id": 2,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "4",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3106,
+ "text_id": "Practice_3106",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": -1,
+ "ai_deck_level": 106,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3001,
+ "text_id": "Practice_3001",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 10,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3002,
+ "text_id": "Practice_3002",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3003,
+ "text_id": "Practice_3003",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3004,
+ "text_id": "Practice_3004",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": 400003,
+ "ai_deck_level": 5,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3005,
+ "text_id": "Practice_3005",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3006,
+ "text_id": "Practice_3006",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3007,
+ "text_id": "Practice_3007",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": 400004,
+ "ai_deck_level": 7,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3101,
+ "text_id": "Practice_3101",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3102,
+ "text_id": "Practice_3102",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3103,
+ "text_id": "Practice_3103",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3104,
+ "text_id": "Practice_3104",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 3105,
+ "text_id": "Practice_3105",
+ "class_id": 3,
+ "chara_id": 3,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "7",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4107,
+ "text_id": "Practice_4107",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": -1,
+ "ai_deck_level": 107,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4001,
+ "text_id": "Practice_4001",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 10,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4002,
+ "text_id": "Practice_4002",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4003,
+ "text_id": "Practice_4003",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4004,
+ "text_id": "Practice_4004",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": 400003,
+ "ai_deck_level": 5,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4005,
+ "text_id": "Practice_4005",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4006,
+ "text_id": "Practice_4006",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4007,
+ "text_id": "Practice_4007",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": 400004,
+ "ai_deck_level": 7,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4101,
+ "text_id": "Practice_4101",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4102,
+ "text_id": "Practice_4102",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4103,
+ "text_id": "Practice_4103",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4104,
+ "text_id": "Practice_4104",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4105,
+ "text_id": "Practice_4105",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 4106,
+ "text_id": "Practice_4106",
+ "class_id": 4,
+ "chara_id": 4,
+ "degree_id": -1,
+ "ai_deck_level": 106,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "3",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5105,
+ "text_id": "Practice_5105",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5001,
+ "text_id": "Practice_5001",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 10,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5002,
+ "text_id": "Practice_5002",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5003,
+ "text_id": "Practice_5003",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5004,
+ "text_id": "Practice_5004",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": 400003,
+ "ai_deck_level": 5,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5005,
+ "text_id": "Practice_5005",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5006,
+ "text_id": "Practice_5006",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5007,
+ "text_id": "Practice_5007",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": 400004,
+ "ai_deck_level": 7,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5101,
+ "text_id": "Practice_5101",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5102,
+ "text_id": "Practice_5102",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5103,
+ "text_id": "Practice_5103",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 5104,
+ "text_id": "Practice_5104",
+ "class_id": 5,
+ "chara_id": 5,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "6",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6109,
+ "text_id": "Practice_6109",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 109,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6001,
+ "text_id": "Practice_6001",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 10,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6002,
+ "text_id": "Practice_6002",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6003,
+ "text_id": "Practice_6003",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6004,
+ "text_id": "Practice_6004",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": 400003,
+ "ai_deck_level": 5,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6005,
+ "text_id": "Practice_6005",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6006,
+ "text_id": "Practice_6006",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6007,
+ "text_id": "Practice_6007",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": 400004,
+ "ai_deck_level": 7,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6101,
+ "text_id": "Practice_6101",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6102,
+ "text_id": "Practice_6102",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6103,
+ "text_id": "Practice_6103",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6104,
+ "text_id": "Practice_6104",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6105,
+ "text_id": "Practice_6105",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6106,
+ "text_id": "Practice_6106",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 106,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6107,
+ "text_id": "Practice_6107",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 107,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 6108,
+ "text_id": "Practice_6108",
+ "class_id": 6,
+ "chara_id": 6,
+ "degree_id": -1,
+ "ai_deck_level": 108,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "2",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7105,
+ "text_id": "Practice_7105",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7001,
+ "text_id": "Practice_7001",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 10,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7002,
+ "text_id": "Practice_7002",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7003,
+ "text_id": "Practice_7003",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7004,
+ "text_id": "Practice_7004",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": 400003,
+ "ai_deck_level": 5,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7005,
+ "text_id": "Practice_7005",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7006,
+ "text_id": "Practice_7006",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7007,
+ "text_id": "Practice_7007",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": 400004,
+ "ai_deck_level": 7,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7101,
+ "text_id": "Practice_7101",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7102,
+ "text_id": "Practice_7102",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7103,
+ "text_id": "Practice_7103",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 7104,
+ "text_id": "Practice_7104",
+ "class_id": 7,
+ "chara_id": 7,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "5",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8107,
+ "text_id": "Practice_8107",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": -1,
+ "ai_deck_level": 107,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8001,
+ "text_id": "Practice_8001",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": 400001,
+ "ai_deck_level": 0,
+ "ai_logic_level": 0,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8002,
+ "text_id": "Practice_8002",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": 400002,
+ "ai_deck_level": 2,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8003,
+ "text_id": "Practice_8003",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": 400003,
+ "ai_deck_level": 3,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8005,
+ "text_id": "Practice_8005",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": 400004,
+ "ai_deck_level": 4,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8006,
+ "text_id": "Practice_8006",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": 400004,
+ "ai_deck_level": 6,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8101,
+ "text_id": "Practice_8101",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": -1,
+ "ai_deck_level": 101,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8102,
+ "text_id": "Practice_8102",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": -1,
+ "ai_deck_level": 102,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8103,
+ "text_id": "Practice_8103",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": -1,
+ "ai_deck_level": 103,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8104,
+ "text_id": "Practice_8104",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": -1,
+ "ai_deck_level": 104,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8105,
+ "text_id": "Practice_8105",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": -1,
+ "ai_deck_level": 105,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ },
+ {
+ "practice_id": 8106,
+ "text_id": "Practice_8106",
+ "class_id": 8,
+ "chara_id": 8,
+ "degree_id": -1,
+ "ai_deck_level": 106,
+ "ai_logic_level": 2,
+ "ai_max_life": 20,
+ "battle3dfield_id": "18",
+ "is_maintenance": false,
+ "is_campaign_practice": false
+ }
+]
diff --git a/SVSim.Bootstrap/Importers/GlobalsImporter.cs b/SVSim.Bootstrap/Importers/GlobalsImporter.cs
index d18f1e9..8a17b5a 100644
--- a/SVSim.Bootstrap/Importers/GlobalsImporter.cs
+++ b/SVSim.Bootstrap/Importers/GlobalsImporter.cs
@@ -31,7 +31,6 @@ public class GlobalsImporter
JsonElement? mypageIndex = LoadCapture(capturesDir, "mypage-index");
JsonElement? deckInfo = LoadCapture(capturesDir, "deck-info");
JsonElement? paymentItemList = LoadCapture(capturesDir, "payment-item-list");
- JsonElement? practiceInfo = LoadCapture(capturesDir, "practice-info");
JsonElement? packInfo = LoadCapture(capturesDir, "pack-info");
JsonElement? basicPuzzleInfo = LoadCapture(capturesDir, "basic-puzzle-info");
JsonElement? basicPuzzleMission = LoadCapture(capturesDir, "basic-puzzle-mission");
@@ -75,11 +74,6 @@ public class GlobalsImporter
total += await ImportPaymentItems(context, paymentItemList.Value);
}
- if (practiceInfo.HasValue)
- {
- total += await ImportPracticeOpponents(context, practiceInfo.Value);
- }
-
if (packInfo.HasValue)
{
total += await ImportPacks(context, packInfo.Value);
@@ -879,47 +873,6 @@ public class GlobalsImporter
return created + updated;
}
- // ---------- Practice Opponents ----------
-
- ///
- /// Capture is the full /practice/info envelope; data is a JSON ARRAY (not an object,
- /// unlike most endpoints). Each row is one AI opponent row keyed on practice_id. Prod sends
- /// numeric fields as strings — GetInt tolerates both. Rows present in the DB but missing
- /// from the capture are LEFT INTACT (consistent with the rest of GlobalsImporter; partial
- /// captures shouldn't silently delete entries).
- ///
- private async Task ImportPracticeOpponents(SVSimDbContext context, JsonElement practiceData)
- {
- if (practiceData.ValueKind != JsonValueKind.Array) return 0;
-
- var existing = await context.PracticeOpponents.ToDictionaryAsync(e => e.Id);
- int created = 0, updated = 0;
-
- foreach (var row in practiceData.EnumerateArray())
- {
- int practiceId = GetInt(row, "practice_id");
- if (practiceId == 0) continue; // malformed row
-
- var entry = existing.TryGetValue(practiceId, out var ex) ? ex : new PracticeOpponentEntry { Id = practiceId };
- entry.TextId = GetString(row, "text_id");
- entry.ClassId = GetInt(row, "class_id");
- entry.CharaId = GetInt(row, "chara_id");
- entry.DegreeId = GetInt(row, "degree_id");
- entry.AiDeckLevel = GetInt(row, "ai_deck_level");
- entry.AiLogicLevel = GetInt(row, "ai_logic_level");
- entry.AiMaxLife = GetInt(row, "ai_max_life");
- entry.Battle3dFieldId = GetString(row, "battle3dfield_id", "1");
- entry.IsMaintenance = GetBool(row, "is_maintenance");
- entry.IsCampaignPractice = GetBool(row, "is_campaign_practice");
-
- if (ex is null) { context.PracticeOpponents.Add(entry); created++; }
- else updated++;
- }
-
- Console.WriteLine($"[GlobalsImporter] PracticeOpponents: +{created}/~{updated}");
- return created + updated;
- }
-
// ---------- Basic Puzzle Groups + Puzzles ----------
///
diff --git a/SVSim.Bootstrap/Importers/PracticeOpponentImporter.cs b/SVSim.Bootstrap/Importers/PracticeOpponentImporter.cs
new file mode 100644
index 0000000..81e6a1f
--- /dev/null
+++ b/SVSim.Bootstrap/Importers/PracticeOpponentImporter.cs
@@ -0,0 +1,54 @@
+using Microsoft.EntityFrameworkCore;
+using SVSim.Bootstrap.Models.Seed;
+using SVSim.Database;
+using SVSim.Database.Models;
+
+namespace SVSim.Bootstrap.Importers;
+
+///
+/// Idempotent upsert of practice opponents from seeds/practice-opponents.json.
+/// Rows missing from the seed are LEFT INTACT (consistent with the previous import behavior;
+/// a partial seed shouldn't silently delete entries).
+///
+public class PracticeOpponentImporter
+{
+ public async Task ImportAsync(SVSimDbContext context, string seedDir)
+ {
+ string path = Path.Combine(seedDir, "practice-opponents.json");
+ var seed = SeedLoader.LoadList(path);
+ if (seed.Count == 0)
+ {
+ Console.WriteLine("[PracticeOpponentImporter] No seed rows; skipping.");
+ return 0;
+ }
+
+ var existing = await context.PracticeOpponents.ToDictionaryAsync(e => e.Id);
+ int created = 0, updated = 0;
+
+ foreach (var s in seed)
+ {
+ if (s.PracticeId == 0) continue;
+
+ var entry = existing.TryGetValue(s.PracticeId, out var ex)
+ ? ex : new PracticeOpponentEntry { Id = s.PracticeId };
+
+ entry.TextId = s.TextId;
+ entry.ClassId = s.ClassId;
+ entry.CharaId = s.CharaId;
+ entry.DegreeId = s.DegreeId;
+ entry.AiDeckLevel = s.AiDeckLevel;
+ entry.AiLogicLevel = s.AiLogicLevel;
+ entry.AiMaxLife = s.AiMaxLife;
+ entry.Battle3dFieldId = s.Battle3dFieldId;
+ entry.IsMaintenance = s.IsMaintenance;
+ entry.IsCampaignPractice = s.IsCampaignPractice;
+
+ if (ex is null) { context.PracticeOpponents.Add(entry); created++; }
+ else updated++;
+ }
+
+ await context.SaveChangesAsync();
+ Console.WriteLine($"[PracticeOpponentImporter] +{created}/~{updated}");
+ return created + updated;
+ }
+}
diff --git a/SVSim.Bootstrap/Models/Seed/PracticeOpponentSeed.cs b/SVSim.Bootstrap/Models/Seed/PracticeOpponentSeed.cs
new file mode 100644
index 0000000..06f9959
--- /dev/null
+++ b/SVSim.Bootstrap/Models/Seed/PracticeOpponentSeed.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace SVSim.Bootstrap.Models.Seed;
+
+public sealed class PracticeOpponentSeed
+{
+ [JsonPropertyName("practice_id")] public int PracticeId { get; set; }
+ [JsonPropertyName("text_id")] public string TextId { get; set; } = "";
+ [JsonPropertyName("class_id")] public int ClassId { get; set; }
+ [JsonPropertyName("chara_id")] public int CharaId { get; set; }
+ [JsonPropertyName("degree_id")] public int DegreeId { get; set; }
+ [JsonPropertyName("ai_deck_level")] public int AiDeckLevel { get; set; }
+ [JsonPropertyName("ai_logic_level")] public int AiLogicLevel { get; set; }
+ [JsonPropertyName("ai_max_life")] public int AiMaxLife { get; set; }
+ [JsonPropertyName("battle3dfield_id")] public string Battle3dFieldId { get; set; } = "1";
+ [JsonPropertyName("is_maintenance")] public bool IsMaintenance { get; set; }
+ [JsonPropertyName("is_campaign_practice")] public bool IsCampaignPractice { get; set; }
+}
diff --git a/SVSim.Bootstrap/Program.cs b/SVSim.Bootstrap/Program.cs
index e6f2487..6e2dd7a 100644
--- a/SVSim.Bootstrap/Program.cs
+++ b/SVSim.Bootstrap/Program.cs
@@ -76,6 +76,7 @@ public static class Program
if (!opts.SkipGlobals)
{
await new GlobalsImporter().ImportAllAsync(context, opts.CapturesDir);
+ await new PracticeOpponentImporter().ImportAsync(context, opts.SeedDir);
// BuildDeck pipeline: series CSV → catalog JSON → package CSV. Catalog must run after
// series CSV (FK on products → series) and before package CSV (so the catalog-side
@@ -152,6 +153,7 @@ public static class Program
string refDir = referenceDataDir ?? shippedDataDir;
string shippedStoryDir = Path.Combine(shippedDataDir, "story");
string storyDir = storyDataDir ?? shippedStoryDir;
+ string shippedSeedDir = Path.Combine(shippedDataDir, "seeds");
string connStr = connection
?? Environment.GetEnvironmentVariable("NPGSQL_CONNECTION")
@@ -159,7 +161,7 @@ public static class Program
return new BootstrapOptions(
cardsFile, capturesDir, refDir, connStr, skipReference, skipCards, skipGlobals,
- skipStory, storyDir);
+ skipStory, storyDir, shippedSeedDir);
}
private static string NextArg(string[] args, ref int i)
@@ -204,5 +206,6 @@ public static class Program
bool SkipCards,
bool SkipGlobals,
bool SkipStory,
- string StoryDataDir);
+ string StoryDataDir,
+ string SeedDir);
}
diff --git a/SVSim.UnitTests/Importers/PracticeOpponentImporterTests.cs b/SVSim.UnitTests/Importers/PracticeOpponentImporterTests.cs
new file mode 100644
index 0000000..3f1ed68
--- /dev/null
+++ b/SVSim.UnitTests/Importers/PracticeOpponentImporterTests.cs
@@ -0,0 +1,41 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using SVSim.Bootstrap.Importers;
+using SVSim.Database;
+using SVSim.UnitTests.Infrastructure;
+
+namespace SVSim.UnitTests.Importers;
+
+public class PracticeOpponentImporterTests
+{
+ private static string SeedDir => Path.Combine(AppContext.BaseDirectory, "Data", "seeds");
+
+ [Test]
+ public async Task Imports_opponents_from_seed_file()
+ {
+ using var factory = new SVSimTestFactory();
+ using var scope = factory.Services.CreateScope();
+ var db = scope.ServiceProvider.GetRequiredService();
+
+ await new PracticeOpponentImporter().ImportAsync(db, SeedDir);
+
+ var opponents = await db.PracticeOpponents.OrderBy(p => p.Id).ToListAsync();
+ Assert.That(opponents.Count, Is.GreaterThan(0), "seed file must contain opponents");
+ Assert.That(opponents.All(o => o.ClassId >= 0), Is.True);
+ }
+
+ [Test]
+ public async Task Is_idempotent_on_rerun()
+ {
+ using var factory = new SVSimTestFactory();
+ using var scope = factory.Services.CreateScope();
+ var db = scope.ServiceProvider.GetRequiredService();
+
+ await new PracticeOpponentImporter().ImportAsync(db, SeedDir);
+ int before = await db.PracticeOpponents.CountAsync();
+ await new PracticeOpponentImporter().ImportAsync(db, SeedDir);
+ int after = await db.PracticeOpponents.CountAsync();
+
+ Assert.That(after, Is.EqualTo(before));
+ }
+}
diff --git a/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs b/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs
index c91c3d6..899883a 100644
--- a/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs
+++ b/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs
@@ -185,9 +185,14 @@ internal sealed class SVSimTestFactory : WebApplicationFactory
public async Task SeedGlobalsAsync(string? capturesDir = null)
{
capturesDir ??= Path.Combine(AppContext.BaseDirectory, "Data", "prod-captures");
+ string seedDir = Path.Combine(AppContext.BaseDirectory, "Data", "seeds");
using var scope = Services.CreateScope();
var ctx = scope.ServiceProvider.GetRequiredService();
await new GlobalsImporter().ImportAllAsync(ctx, capturesDir);
+ // Per-importer seed pipeline runs alongside GlobalsImporter during the migration.
+ // Wired here so SeedGlobalsAsync callers (e.g. PracticeControllerTests) still see
+ // practice-opponent rows after the corresponding block was lifted out of GlobalsImporter.
+ await new PracticeOpponentImporter().ImportAsync(ctx, seedDir);
}
/// Convenience: bake the X-Test-Viewer-Id header into a fresh client.
diff --git a/SVSim.UnitTests/SVSim.UnitTests.csproj b/SVSim.UnitTests/SVSim.UnitTests.csproj
index 4b5cbc4..244ede9 100644
--- a/SVSim.UnitTests/SVSim.UnitTests.csproj
+++ b/SVSim.UnitTests/SVSim.UnitTests.csproj
@@ -42,6 +42,11 @@
PreserveNewest
+
+
+ PreserveNewest
+