LLM sometimes returns null instead of {} for action_params when an
action doesn't require parameters (e.g., ["Cultivate", null]). This
caused AttributeError when calling .items() on None.
Changes:
- Add defensive check in ai.py to convert null to {}
- Update prompt to explicitly require {} instead of null
- Add validate_target_avatar() to TargetingMixin for unified validation.
- Update Attack and Assassinate to use the new validation method.
- Add comment to MutualAction.can_start() explaining why it uses inline check.
- Add tests for dead target validation.
The set_relation(from, to, rel) means "from views to as rel".
When avatar (student) takes master (teacher), avatar should view
master as MASTER, not APPRENTICE.
Before: avatar.set_relation(master, APPRENTICE) - wrong direction
After: avatar.set_relation(master, MASTER) - correct direction
Use str(a.gender) instead of a.gender.value to return Chinese
"男"/"女" instead of English "male"/"female" in the avatar_list
endpoint, consistent with other APIs that use get_structured_info().
On macOS/Linux, using shell=True with a list argument doesn't work as expected.
Only the first element is passed to the shell, causing npm to print help instead
of running the dev command.
Changes:
- Use shell=False + list on macOS/Linux, shell=True + string on Windows.
- Use terminate() instead of taskkill on macOS/Linux for cleanup.