use serde_json::json; use shared_contracts::runtime_story::{ RuntimeStoryActionRequest, RuntimeStoryChoiceAction, RuntimeStoryPatch, }; use crate::{ battle::resolve_battle_action, build_status_patch, read_bool_field, read_i32_field, read_optional_string_field, }; fn build_battle_fixture() -> serde_json::Value { json!({ "inBattle": true, "npcInteractionActive": false, "playerHp": 4, "playerMaxHp": 40, "playerMana": 10, "playerMaxMana": 10, "playerSkillCooldowns": {}, "runtimeStats": { "hostileNpcsDefeated": 0, "itemsUsed": 0, "questsAccepted": 0, "scenesTraveled": 0, "playTimeMs": 0, "lastPlayTickAt": null }, "currentNpcBattleMode": "fight", "currentNpcBattleOutcome": null, "currentEncounter": { "kind": "npc", "id": "npc_bandit_01", "npcName": "断桥匪首", "hostile": true, "hp": 8, "experienceReward": 24 }, "sceneHostileNpcs": [{ "id": "npc_bandit_01", "name": "断桥匪首", "hp": 8, "maxHp": 80, "experienceReward": 24 }] }) } fn build_request(function_id: &str, option_text: &str) -> RuntimeStoryActionRequest { RuntimeStoryActionRequest { session_id: "runtime-main".to_string(), client_version: Some(0), action: RuntimeStoryChoiceAction { action_type: "story_choice".to_string(), function_id: function_id.to_string(), target_id: None, payload: Some(json!({ "optionText": option_text })), }, } } #[test] fn battle_resolution_prefers_player_defeat_when_both_sides_fall_in_same_turn() { let request = build_request("battle_all_in_crush", "全力压制"); let mut game_state = build_battle_fixture(); let resolution = resolve_battle_action(&mut game_state, &request, "battle_all_in_crush") .expect("battle action should resolve"); assert_eq!(read_i32_field(&game_state, "playerHp"), Some(0)); assert_eq!( read_optional_string_field(&game_state, "currentNpcBattleOutcome"), Some("fight_defeat".to_string()) ); assert_eq!(read_bool_field(&game_state, "inBattle"), Some(false)); assert!(resolution.result_text.contains("败北")); assert!(matches!( resolution.patches.first(), Some(RuntimeStoryPatch::BattleResolved { outcome, .. }) if outcome == "defeat" )); assert_eq!( resolution.patches.get(1), Some(&build_status_patch(&game_state)) ); assert_eq!( resolution.battle.and_then(|battle| battle.outcome), Some("defeat".to_string()) ); }