diff --git a/audit/manifest.json b/audit/manifest.json index 1f5f3705..ed2d15d6 100644 --- a/audit/manifest.json +++ b/audit/manifest.json @@ -684,6 +684,14 @@ "tests": [ "project/app_flow_lifecycle_test.gd" ] + }, + { + "reference": "CPythonApplication::Process on the 60 Hz s_uiNextFrameTime update clock; a late frame skips only the render (s_bFrameSkip), so CPythonNetworkStream::Process runs every 16/17 ms", + "implementation": "src/platform/UserInterface/PythonApplication.cpp CPythonApplication::Process: one host frame polls the stream once per elapsed 60 Hz tick (catch-up capped at 500 ms)", + "invariant": "GamePhase packet budget (3 packets a poll below 8 KB pending) must be 180/s regardless of render rate; at 30 fps idle it was 90/s and mob move packets queued behind character add/del bursts", + "tests": [ + "real server 192.168.21.203 chase probe A/B 2026-09-30: GC_MOVE push gap p50 138-178 -> 70-92 ms, p90 580-785 -> 358-403 ms" + ] } ], "evidence": { diff --git a/src/platform/UserInterface/PythonApplication.cpp b/src/platform/UserInterface/PythonApplication.cpp index 5ca9f711..1d5977f5 100644 --- a/src/platform/UserInterface/PythonApplication.cpp +++ b/src/platform/UserInterface/PythonApplication.cpp @@ -330,7 +330,23 @@ bool CPythonApplication::Process() // Network I/O { MtPerf::CScope kPerf(MtPerf::SECTION_NETWORK); - CPythonNetworkStream::Instance().Process(); + // PORT: 40250 runs Process() on its 60 Hz update clock and only skips the render pass when late + // (s_bFrameSkip), so the stream is polled every 16 ms whatever the render rate, and GamePhase + // handles three packets a poll while less than 8 KB is pending. The host runs one Process() per + // rendered frame (30 fps when idle, less on a busy phone), which halves that budget or worse: the + // character add/del bursts of a mob field hold the move packets back for up to a second and the + // mobs stop, then jump. Poll once per elapsed 60 Hz tick, as 40250 does. + { + static DWORD s_dwNextNetworkPoll = 0; + const DWORD dwNow = ELTimer_GetMSec(); + if (!s_dwNextNetworkPoll || int(dwNow - s_dwNextNetworkPoll) > 500) + s_dwNextNetworkPoll = dwNow; + do + { + CPythonNetworkStream::Instance().Process(); + s_dwNextNetworkPoll += 16 + (s_dwNextNetworkPoll & 1); + } while (int(dwNow - s_dwNextNetworkPoll) >= 0); + } // PORT: m_kGuildMarkUploader/m_kGuildMarkDownloader.Process() follow here in 40250; the guild mark // transfer is not ported yet (GuildMarkDownloader.cpp/GuildMarkUploader.cpp). CAccountConnector::Instance().Process();