@@ -47,16 +47,16 @@ signal local_player_spawned(entity: Entity)
4747# ============================================================================
4848
4949var _world : World
50- var _applying_network_data : bool = false # Prevents sync loops — CRITICAL
51- var _broadcast_pending : Dictionary = {} # Deferred spawn guard — CRITICAL
50+ var _applying_network_data : bool = false # Prevents sync loops — CRITICAL
51+ var _broadcast_pending : Dictionary = {} # Deferred spawn guard — CRITICAL
5252var _spawn_counter : int = 0
53- var _game_session_id : int = 0 # Session anti-ghost — CRITICAL
53+ var _game_session_id : int = 0 # Session anti-ghost — CRITICAL
5454var _spawn_manager : SpawnManager
5555var _sender : SyncSender
5656var _receiver : SyncReceiver
5757var _native_sync_handler : NativeSyncHandler
58- var _relationship_handler # SyncRelationshipHandler (untyped — no class_name)
59- var _reconciliation_handler # SyncReconciliationHandler (untyped — no class_name)
58+ var _relationship_handler # SyncRelationshipHandler (untyped — no class_name)
59+ var _reconciliation_handler # SyncReconciliationHandler (untyped — no class_name)
6060var _is_ready : bool = false
6161
6262# ============================================================================
@@ -68,7 +68,7 @@ var _is_ready: bool = false
6868## CRITICAL: sets name before add_child() so RPC routing is consistent across peers.
6969static func attach_to_world (world : World , net_adapter : NetAdapter = null ) -> NetworkSync :
7070 var net_sync = NetworkSync .new ()
71- net_sync .name = "NetworkSync" # CRITICAL for RPC routing
71+ net_sync .name = "NetworkSync" # CRITICAL for RPC routing
7272 if net_adapter != null :
7373 net_sync .net_adapter = net_adapter
7474 world .add_child (net_sync )
@@ -83,7 +83,7 @@ static func attach_to_world(world: World, net_adapter: NetAdapter = null) -> Net
8383func _ready () -> void :
8484 # Fallback name guard — ensures RPC routing works even when not using factory
8585 if name .begins_with ("@" ):
86- name = "NetworkSync" # CRITICAL
86+ name = "NetworkSync" # CRITICAL
8787
8888 if net_adapter == null :
8989 net_adapter = NetAdapter .new ()
@@ -93,14 +93,14 @@ func _ready() -> void:
9393 push_error ("NetworkSync: parent must be a World node" )
9494 return
9595
96- _spawn_manager = SpawnManager .new (self )
97- _sender = SyncSender .new (self )
98- _receiver = SyncReceiver .new (self )
99- _native_sync_handler = NativeSyncHandler .new (self )
96+ _spawn_manager = SpawnManager .new (self )
97+ _sender = SyncSender .new (self )
98+ _receiver = SyncReceiver .new (self )
99+ _native_sync_handler = NativeSyncHandler .new (self )
100100 var SyncRelationshipHandlerScript = load ("res://addons/gecs/network/sync_relationship_handler.gd" )
101- _relationship_handler = SyncRelationshipHandlerScript .new (self )
101+ _relationship_handler = SyncRelationshipHandlerScript .new (self )
102102 var SyncReconciliationHandlerScript = load ("res://addons/gecs/network/sync_reconciliation_handler.gd" )
103- _reconciliation_handler = SyncReconciliationHandlerScript .new (self )
103+ _reconciliation_handler = SyncReconciliationHandlerScript .new (self )
104104
105105 _world .entity_added .connect (_on_entity_added )
106106 _world .entity_removed .connect (_on_entity_removed )
@@ -124,7 +124,7 @@ func _exit_tree() -> void:
124124## Reset NetworkSync state for a new game instance.
125125## Call this when returning to lobby/menu to ensure clean state for next game.
126126func reset_for_new_game () -> void :
127- _game_session_id += 1 # Monotonic increment invalidates all in-flight RPCs
127+ _game_session_id += 1 # Monotonic increment invalidates all in-flight RPCs
128128 _broadcast_pending .clear ()
129129 _spawn_counter = 0
130130 if _relationship_handler != null :
@@ -225,8 +225,8 @@ func register_receive_handler(comp_type_name: String, handler: Callable) -> void
225225
226226func _process (delta : float ) -> void :
227227 if _world == null or not net_adapter .is_in_game ():
228- return # Zero overhead in single-player — FOUND-03
229- _sender .tick (delta ) # Phase 2: priority-tiered property sync
228+ return # Zero overhead in single-player — FOUND-03
229+ _sender .tick (delta ) # Phase 2: priority-tiered property sync
230230 if _reconciliation_handler != null :
231231 _reconciliation_handler .tick (delta )
232232
@@ -277,7 +277,9 @@ func _on_entity_added(entity: Entity) -> void:
277277func _on_entity_removed (entity : Entity ) -> void :
278278 if not net_adapter .is_in_game ():
279279 return
280- _spawn_manager .on_entity_removed (entity )
280+ # Server-only: broadcast despawn to clients (mirrors _on_entity_added guard)
281+ if net_adapter .is_server ():
282+ _spawn_manager .on_entity_removed (entity )
281283
282284
283285# ============================================================================
@@ -317,7 +319,7 @@ func _deferred_broadcast(entity: Entity, entity_id: String) -> void:
317319 _broadcast_pending .erase (entity_id )
318320 return
319321 if not _broadcast_pending .has (entity_id ):
320- return # Cancelled by on_entity_removed before deferred call fired
322+ return # Cancelled by on_entity_removed before deferred call fired
321323 _broadcast_pending .erase (entity_id )
322324 # Server-side setup: inject authority markers and native sync.
323325 # Clients receive these via _apply_component_data in handle_spawn_entity.
0 commit comments