# net_world_respawn_test —— 40250 NetworkActorManager::__AppendCharacterManagerActor # 重建边界:普通同 VID 重建使用新包坐标;只有上下马切换保留旧实例坐标。 extends SceneTree const NetWorld = preload("res://net_world.gd") const FakeClient = preload("res://net_client_fake.gd") var _fail := 0 func _ck(ok: bool, message: String) -> void: if not ok: _fail += 1 printerr("FAIL: " + message) func _init() -> void: await _run() if _fail == 0: print("PASS: net_world_respawn_test (spawn overwrite / mount transition)") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _run() -> void: var mount := Node3D.new() get_root().add_child(mount) var client := FakeClient.new() get_root().add_child(client) var world: Node = NetWorld.new() get_root().add_child(world) world.setup(client, mount) world.set_process(false) await process_frame var first := { "vid": 42, "name": "mob", "race": 101, "kind": 2, "ch_type": 2, "pos": Vector3(1.0, 0.0, -2.0), "pos_cm": Vector3(100.0, 200.0, 0.0), "angle_deg": 0.0, "moving": false, "func": 0, "walk_mode": 0, "dead": false, "mount_vnum": 0, "affect_flags": 0, "hp": 100, "max_hp": 100, "parts": [0, 0, 0, 0] } client.entities[42] = first client.entity_spawned.emit(first) var node: Node3D = world.node_for(42) _ck(node != null, "first spawn creates the actor node") if node == null: return _ck(node.position.is_equal_approx(Vector3(first.pos.x, first.pos.y, -first.pos.z)), "first spawn uses incoming network position") # Reference __AppendCharacterManagerActor uses the new SCreateData position # when the mount state is unchanged. The old node position must not win. var normal_readd := first.duplicate(true) normal_readd["pos"] = Vector3(7.0, 0.0, -8.0) normal_readd["pos_cm"] = Vector3(700.0, 800.0, 0.0) normal_readd["angle_deg"] = 90.0 client.entities[42] = normal_readd client.entity_spawned.emit(normal_readd) var rebuilt: Node3D = world.node_for(42) _ck(rebuilt != null and rebuilt != node, "same VID re-add recreates the scene instance") if rebuilt == null: return _ck(rebuilt.position.is_equal_approx(Vector3(normal_readd.pos.x, normal_readd.pos.y, -normal_readd.pos.z)), "ordinary re-add uses the new authoritative position") _ck(is_equal_approx(rebuilt.rotation.y, deg_to_rad(90.0)), "ordinary re-add uses the new authoritative rotation") # 40250 preserves the old pixel position only when switching mount state. var before_mount: Vector3 = rebuilt.position var mount_readd := normal_readd.duplicate(true) mount_readd["pos"] = Vector3(12.0, 0.0, -13.0) mount_readd["pos_cm"] = Vector3(1200.0, 1300.0, 0.0) mount_readd["mount_vnum"] = 20030 client.entities[42] = mount_readd client.entity_spawned.emit(mount_readd) var mounted: Node3D = world.node_for(42) _ck(mounted != null, "mount transition recreates the scene instance") if mounted != null: _ck(mounted.position.is_equal_approx(before_mount), "mount transition preserves old position while rebuilding") _ck(is_equal_approx(mounted.rotation.y, deg_to_rad(90.0)), "mount transition still uses the incoming rotation") # 40250 RemoveActor uses DeleteInstanceByFade, but a later AppendActor for # the same VID sees no alive instance and creates the replacement immediately; # the old dead-list blend-out must not suppress the new actor. client.entity_despawned.emit(42) _ck(world.node_for(42) == null, "despawn removes the live node while it fades") var reused := mount_readd.duplicate(true) reused["pos"] = Vector3(20.0, 0.0, -21.0) reused["pos_cm"] = Vector3(2000.0, 2100.0, 0.0) reused["mount_vnum"] = 0 client.entities[42] = reused client.entity_spawned.emit(reused) var replacement: Node3D = world.node_for(42) _ck(replacement != null, "same VID spawn is rebuilt during the old fade") if replacement != null: _ck(replacement.position.is_equal_approx(Vector3(reused.pos.x, reused.pos.y, -reused.pos.z)), "same VID replacement uses its new authoritative position") await get_root().get_tree().create_timer(0.5).timeout _ck(world.node_for(42) == replacement, "old fade completion does not remove the replacement") # 40250 removes the selected/main actor through DeleteInstance, not the # delayed dead-list blend-out path used for ordinary remote actors. var main_data := reused.duplicate(true) main_data["vid"] = 7 main_data["name"] = "main" client.entities[7] = main_data world._main_vid = 7 client.entity_spawned.emit(main_data) var main_node: Node3D = world.node_for(7) _ck(main_node != null, "main actor spawn creates the selected node") client.entity_despawned.emit(7) _ck(not world._fading.has(7), "main actor despawn skips DeleteInstanceByFade") _ck(world.node_for(7) == null, "main actor despawn removes the node immediately") world.free() client.free() mount.free() await process_frame