# net_trace —— 主角 vid 生命周期取证。 # # 实机进游戏后画面全灰、主角是蓝胶囊:GAMESCENE_DIAG 显示地图正常装载, # 但 main_vid=0、主角实体为空 —— 主角在 setup() 之后被从 EntityStore 里 # 摘掉了。C++ 侧能把 m_main_vid 清零的路只有三条(mut_despawn / # m2dev GC_CHARACTER_DEL / reset_for_map_change),前两条会发 # entity_despawned 信号,第三条会清空 m_changes、悄悄归零。 # 这个节点每帧轮询 main_vid 并记录信号,于是「哪条路」直接可读: # MAIN_LOST reason=despawn → GC_CHARACTER_DEL 命中了主角 # MAIN_LOST reason=silent → reset_for_map_change(warp)或别的静默清零 # # 常驻但极廉价(一次 get_main_vid + 一个整数比较);只在事件发生时打印。 extends Node const RING := 24 var _client: Node = null var _main_vid := 0 var _t0 := 0 var _ring: Array[String] = [] var _verbose := false func setup(client: Node) -> void: _client = client _t0 = Time.get_ticks_msec() # 主角在进场那几秒会被服务器 DEL+ADD 若干次(参考端同样如此),逐条打太吵, # 默认只报异常;MT_NETTRACE=1 才打全量。 _verbose = OS.get_environment("MT_NETTRACE") == "1" for sig in ["entity_main_set", "entity_despawned", "entity_spawned", "entered_game", "disconnected", "stage_changed", "phase_changed", "world_reset", "warp"]: if client.has_signal(sig): client.connect(sig, Callable(self, "_on_" + sig)) func _ms() -> int: return Time.get_ticks_msec() - _t0 func _note(line: String) -> void: var s := "+%dms %s" % [_ms(), line] _ring.append(s) if _ring.size() > RING: _ring.pop_front() func _dump(head: String) -> void: printerr("NETTRACE: ", head) for s in _ring: printerr("NETTRACE: ", s) func _on_entity_main_set(vid: int) -> void: _note("main_set vid=%d" % vid) if _verbose: printerr("NETTRACE: MAIN_SET vid=%d t=+%dms" % [vid, _ms()]) func _on_entity_spawned(entity: Dictionary) -> void: var vid := int(entity.get("vid", 0)) if vid == _main_vid or bool(entity.get("is_main", false)): _note("spawn vid=%d race=%s is_main=%s" % [vid, str(entity.get("race", null)), str(entity.get("is_main", false))]) func _on_entity_despawned(vid: int) -> void: _note("despawn vid=%d" % vid) if vid == _main_vid: _note("*** despawn hit MAIN vid=%d" % vid) func _on_entered_game() -> void: _note("entered_game") func _on_disconnected(reason: String) -> void: _note("disconnected " + reason) printerr("NETTRACE: DISCONNECTED %s t=+%dms" % [reason, _ms()]) func _on_world_reset() -> void: _note("world_reset (reset_for_map_change)") func _on_warp(pos: Vector3, _reconnect: bool) -> void: _note("warp " + str(pos)) func _on_stage_changed(stage: String) -> void: _note("stage=" + stage) func _on_phase_changed(phase: String) -> void: _note("phase=" + phase) func _process(_dt: float) -> void: if _client == null or not _client.has_method("get_main_vid"): return var vid := int(_client.get_main_vid()) if vid == _main_vid: return if vid != 0 and _main_vid == 0: _main_vid = vid _note("main_vid 0 -> %d" % vid) return # 丢主角:把最近的事件环一起打出来,能读出是 despawn 还是静默清零。 var was := _main_vid _main_vid = vid var reason := "silent" for s in _ring: if s.ends_with("despawn hit MAIN vid=%d" % was): reason = "despawn" _dump("MAIN_LOST was=%d now=%d reason=%s t=+%dms" % [was, vid, reason, _ms()])