from __future__ import annotations from npc_authority import NpcAuthorityManager, ScopeKey, scope_from_transform def test_first_player_in_scope_becomes_authority() -> None: manager = NpcAuthorityManager() scope = ScopeKey("00000010", "0000003C") changes = manager.reconcile([(7, scope), (4, scope)]) assert len(changes) == 1 assignment = manager.get(scope) assert assignment is not None assert assignment.player_id == 4 assert assignment.epoch == 1 assert manager.authorize(4, scope, 1) assert not manager.authorize(7, scope, 1) def test_disconnect_reassigns_and_increments_epoch() -> None: manager = NpcAuthorityManager() scope = ScopeKey("00000010", "0000003C") manager.reconcile([(1, scope), (2, scope)]) changes = manager.reconcile([(2, scope)]) assert len(changes) == 1 assert changes[0].previous_player_id == 1 assert changes[0].player_id == 2 assert changes[0].epoch == 2 assert not manager.authorize(1, scope, 1) assert manager.authorize(2, scope, 2) def test_two_scopes_have_independent_authorities() -> None: manager = NpcAuthorityManager() a = ScopeKey("00000010", "0000003C") b = ScopeKey("00000020", "0000003C") manager.reconcile([(5, a), (2, a), (7, b), (9, b)]) assert manager.get(a).player_id == 2 assert manager.get(b).player_id == 7 assert manager.get(a).epoch == 1 assert manager.get(b).epoch == 1 def test_empty_scope_revokes_and_future_reentry_uses_new_epoch() -> None: manager = NpcAuthorityManager() scope = ScopeKey("00000010", "") manager.reconcile([(1, scope)]) revoked = manager.reconcile([]) assert revoked[0].player_id == 0 assert revoked[0].epoch == 2 assert manager.get(scope) is None granted = manager.reconcile([(3, scope)]) assert granted[0].player_id == 3 assert granted[0].epoch == 3 def test_scope_from_transform_normalizes_form_ids() -> None: scope = scope_from_transform({"cellId": "1a", "worldspaceId": "3c"}) assert scope == ScopeKey("0000001A", "0000003C") assert scope_from_transform(None) is None assert scope_from_transform({"cellId": ""}) is None