From bf13cdf2d2f51b0f36ed6de72d34c1245d802454 Mon Sep 17 00:00:00 2001 From: bridge Date: Mon, 19 Jan 2026 21:14:20 +0800 Subject: [PATCH] fix: incorrect relationship for new avatar --- src/sim/new_avatar.py | 16 ++-- tests/test_new_avatar_relation.py | 126 ++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 tests/test_new_avatar_relation.py diff --git a/src/sim/new_avatar.py b/src/sim/new_avatar.py index 736869e..457184d 100644 --- a/src/sim/new_avatar.py +++ b/src/sim/new_avatar.py @@ -236,7 +236,9 @@ class PopulationPlanner: planned_surname[a] = surname planned_surname[b] = surname planned_gender[a] = Gender.MALE - planned_relations[(a, b)] = Relation.CHILD + # 设定 a 为父,b 为子 + # (a, b) = PARENT -> a.relations[b] = PARENT (a 视 b 为子) + planned_relations[(a, b)] = Relation.PARENT else: mother = a if random.random() < 0.5 else b child = b if mother == a else a @@ -248,7 +250,8 @@ class PopulationPlanner: if s != mom_surname: planned_surname[child] = s break - planned_relations[(mother, child)] = Relation.CHILD + # (mother, child) = PARENT -> mother.relations[child] = PARENT + planned_relations[(mother, child)] = Relation.PARENT leftover = unused_indices[:] @@ -448,10 +451,11 @@ class AvatarFactory: if attach_relations: if plan.parent_avatar is not None: # plan.parent_avatar 是长辈 - # 设置关系:长辈.set_relation(自己, CHILD) - # 底层逻辑:长辈.relations[自己] = CHILD (长辈认为自己是孩子) - # 自己.relations[长辈] = PARENT (自己认为长辈是父母) - plan.parent_avatar.set_relation(avatar, Relation.CHILD) + # 设置关系:长辈.set_relation(自己, PARENT) + # 底层逻辑:长辈.relations[自己] = PARENT (长辈认为自己是父母 -> 错误,是长辈认为自己是子女?) + # 修正:Relation.PARENT 映射显示为“儿子/女儿”,即 relations[X]=PARENT 意味着 X 是儿子/女儿 + # 所以 plan.parent_avatar.relations[avatar] = PARENT 是正确的,表示 parent 视 avatar 为子女 + plan.parent_avatar.set_relation(avatar, Relation.PARENT) if plan.master_avatar is not None: # plan.master_avatar 是师傅 # 设置关系:师傅.set_relation(自己, APPRENTICE) diff --git a/tests/test_new_avatar_relation.py b/tests/test_new_avatar_relation.py new file mode 100644 index 0000000..53d58c7 --- /dev/null +++ b/tests/test_new_avatar_relation.py @@ -0,0 +1,126 @@ +import pytest +from src.classes.world import World +from src.classes.calendar import MonthStamp +from src.classes.age import Age +from src.classes.avatar import Avatar, Gender +from src.classes.relation import Relation, get_relation_label +from src.classes.cultivation import CultivationProgress, Realm +from src.utils.id_generator import get_avatar_id +from src.sim.new_avatar import create_random_mortal, MortalPlanner, AvatarFactory, PopulationPlanner + +@pytest.fixture +def mock_world(base_world): + return base_world + +def test_single_mortal_relation(mock_world): + """测试单个新角色生成时的亲子关系方向是否正确""" + # 1. 创建一个假设的父母角色 + parent_avatar = Avatar( + world=mock_world, + name="Parent", + id=get_avatar_id(), + birth_month_stamp=MonthStamp(0), + age=Age(100, Realm.Core_Formation), + gender=Gender.FEMALE, + cultivation_progress=CultivationProgress(60), # 金丹期 + pos_x=0, + pos_y=0 + ) + # 加入世界管理器 + mock_world.avatar_manager.register_avatar(parent_avatar) + + # 2. 创建一个新角色,作为子女 + # 我们通过强制指定 parent_avatar 来测试关系设置逻辑 + # 由于 create_random_mortal 内部逻辑有随机性,这里直接使用底层 factory 并构造 plan + + child_age = Age(20, Realm.Qi_Refinement) + plan = MortalPlanner.plan(mock_world, "Child", child_age, level=10, allow_relations=False) + + # 手动指定父母,模拟 random 选中的情况 + plan.parent_avatar = parent_avatar + + child_avatar = AvatarFactory.build_from_plan( + mock_world, + mock_world.month_stamp, + name="Child", + age=child_age, + plan=plan, + attach_relations=True + ) + + # 3. 验证关系 + # 父母看子女:应该是 PARENT (映射为 儿子/女儿) + rel_from_parent = parent_avatar.get_relation(child_avatar) + assert rel_from_parent == Relation.PARENT, f"父母看子女应该是 PARENT, 但得到了 {rel_from_parent}" + + label_from_parent = get_relation_label(rel_from_parent, parent_avatar, child_avatar) + # 因为 child 性别随机,可能是 儿子 或 女儿 + assert label_from_parent in ["儿子", "女儿"], f"父母看子女的称谓错误: {label_from_parent}" + + # 子女看父母:应该是 CHILD (映射为 父亲/母亲) + rel_from_child = child_avatar.get_relation(parent_avatar) + assert rel_from_child == Relation.CHILD, f"子女看父母应该是 CHILD, 但得到了 {rel_from_child}" + + label_from_child = get_relation_label(rel_from_child, child_avatar, parent_avatar) + assert label_from_child == "母亲", f"子女看母亲的称谓错误: {label_from_child}" # parent 是 FEMALE + + +def test_population_planner_relations(mock_world): + """测试批量生成时的亲子关系方向是否正确""" + # 强制生成一组角色,通过大量生成来触发家庭关系 + # 为了提高概率,我们直接调用 PopulationPlanner 内部逻辑或者检查生成后的结果 + + # 尝试生成 20 个角色,期望出现家庭关系 + count = 20 + avatars_dict = PopulationPlanner.plan_group(count, existed_sects=None) + + # 检查计划中的关系 + relations = avatars_dict.relations + + if not relations: + pytest.skip("本次随机未生成任何关系,跳过测试") + return + + found_parent_relation = False + + for (a_idx, b_idx), rel in relations.items(): + if rel == Relation.PARENT: + found_parent_relation = True + # 在 plan_group 中,(a, b) = PARENT 意味着 a 是父母,b 是子女 + # 这里的语义是:a 的 relations 中,对 b 的记录是 PARENT + pass + + # 如果找到了 PARENT 关系,说明代码中使用了 Relation.PARENT 而不是之前的 Relation.CHILD + # 之前的代码是用 Relation.CHILD,修正后应该是 Relation.PARENT + + # 进一步:实际构建角色并验证 + avatars_map = AvatarFactory.build_group(mock_world, mock_world.month_stamp, avatars_dict) + avatars = list(avatars_map.values()) + + # 由于 build_group 返回的是 dict[id, Avatar],且顺序可能打乱,我们需要重新映射 index + # 但我们其实只需要遍历所有 Avatar 检查关系即可 + + for av in avatars: + for target, rel in av.relations.items(): + if rel == Relation.PARENT: + # av 认为是父母 -> target 是子女 + # 验证年龄:父母应该比子女大 + assert av.age.age > target.age.age, f"父母({av.name}, {av.age.age}) 应该比子女({target.name}, {target.age.age}) 大" + + # 验证称谓 + label = get_relation_label(rel, av, target) + assert label in ["儿子", "女儿"] + + elif rel == Relation.CHILD: + # av 认为是子女 -> target 是父母 + # 验证年龄:子女应该比父母小 + assert av.age.age < target.age.age, f"子女({av.name}, {av.age.age}) 应该比父母({target.name}, {target.age.age}) 小" + + # 验证称谓 + label = get_relation_label(rel, av, target) + assert label in ["父亲", "母亲"] + + if not found_parent_relation: + # 如果随机没随到家庭,我们可以认为只要没报错且逻辑通顺就行, + # 或者可以 mock random 来强制覆盖路径,但在集成测试中只要多跑几次通常能覆盖 + pass