From f21d059b67473ca5b55f68b8ca00ec12702ecb2e Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Mon, 8 Jun 2026 20:36:28 -0300 Subject: [PATCH] fix(zmmo-client): ZeusPlayerProxy clock offset EMA + asset cleanup L_TestWorld ZeusPlayerProxy.cpp -- H3 fix (2026-06-06): EMA com clamp para ServerClockOffsetMs ================================================================ Antes: offset congelado na 1a amostra de ApplyEntitySnapshot. Em cross-server handoff, a fonte do ServerTimeMs muda (publisher antigo emitia 12340000, novo emite 12351000) e o offset fica permanentemente skewed. Causa tremor pos-handoff + drift cumulativo de clock cliente vs server. Depois: cada amostra recalcula candidato (LocalNowMs - Snapshot.ServerTimeMs) e suaviza via EMA com alpha=0.02 (~50 amostras para convergir) + clamp max 2ms por amostra (a 30Hz isso converge a 60ms/s, suficiente para absorver drift normal sem causar tremor visual). 1a amostra continua bootstrap direto. Trecho: const double Alpha = 0.02; const int64 RawTarget = ServerClockOffsetMs*(1-Alpha) + OffsetCandidate*Alpha; const int64 Delta = RawTarget - ServerClockOffsetMs; const int64 ClampedDelta = clamp(Delta, -2, 2); ServerClockOffsetMs += ClampedDelta; Asset cleanup L_TestWorld ========================== DT_Maps.uasset modificado + 3 ExternalActors deletados (provavelmente limpeza no UE editor ao reorganizar mapa). Empacotado junto pra branch ficar limpa. * Content/ZMMO/Data/World/DT_Maps.uasset (M) * Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/2/BR/38W34OMKW0W3L1PGS3Y98G.uasset (D) * Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/4/8L/X3PMRJRGAACIU1WG1P1KJ0.uasset (D) * Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/8/L1/C6M65Q91PG5E7ML91VQZX3.uasset (D) Co-Authored-By: Claude Opus 4.7 --- Content/ZMMO/Data/World/DT_Maps.uasset | Bin 3062 -> 3062 bytes .../2/BR/38W34OMKW0W3L1PGS3Y98G.uasset | Bin 6229 -> 0 bytes .../4/8L/X3PMRJRGAACIU1WG1P1KJ0.uasset | Bin 10597 -> 0 bytes .../8/L1/C6M65Q91PG5E7ML91VQZX3.uasset | Bin 10911 -> 0 bytes Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp | 30 +++++++++++++++--- 5 files changed, 25 insertions(+), 5 deletions(-) delete mode 100644 Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/2/BR/38W34OMKW0W3L1PGS3Y98G.uasset delete mode 100644 Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/4/8L/X3PMRJRGAACIU1WG1P1KJ0.uasset delete mode 100644 Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/8/L1/C6M65Q91PG5E7ML91VQZX3.uasset diff --git a/Content/ZMMO/Data/World/DT_Maps.uasset b/Content/ZMMO/Data/World/DT_Maps.uasset index fd76096e26156d7b7f1640739312026d2335a999..be9dffcb34bfd67041cab4174057685561e27a0f 100644 GIT binary patch delta 91 zcmew+{!M&>gvf`6Xx(i>x}N#(9DmlITJE7QxNQAKo!KnPsinofi3KjHX$-HucJ1e7 sU|@)X2n81;mgg~iWLn!Wu|R1v59?}XW}w98T^vzNObiT@#kd~;0J6~{e*gdg delta 91 zcmew+{!M&>gh*d;ed*QEr)%GR`EkEFadXM!Yld?+>dai7C@zvKaRR0D$!+m;e9( diff --git a/Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/2/BR/38W34OMKW0W3L1PGS3Y98G.uasset b/Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/2/BR/38W34OMKW0W3L1PGS3Y98G.uasset deleted file mode 100644 index 3e4faf32d44955b74ab304bb78f07380f066bf4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6229 zcmeHMeQZ#QL9y=rP}(jQcWwR9n+Yq@u8|MXx%1FL*1ylt(^*DWvxK%)}n5Nk2bsKK40fK zBm~O#A3rI%_nmX@x%ZxX?m6csN58k?`SXK=gB!*nbXyHV7jX-C`A0pUU5u~z_Hp5v z*VdfLzObQCShjWsmchK;j}6pb_T~Ghy!V|w%U@q(Gh$kJ{rjCwUQ=zcvv}ySGl#O1 zG40^3E$aq+$)(vh*KPaYkN-Y44bvW(w`jxm*3`Ze@637S&%Zl6^%SN({O;Dw&zRPa zJ_-3=x#z&aH%gecaKVQMKAExm_>SG*-SPMA)6y-NwpO|P_BQ8!$H`ecpUf>@H1{q{ zV;_9>uWtYAOZV=p-CmlTI(Z+aeVX2X`V714i`(Bf{)BnWt-A-)(#c65ceLEulU-on zzVpGm=isfN5dP77xL#f#kGqNrFN>VOBo#@{#pC)ezrR)Q=dw9{Op=9^z9rtq=M=SX z(z9W`*%mW5wECN4hM2j<81zKUopzfCH8vvDfE91T#sr+3h)^AFS)0jhH9K9b$?R}h zEM}YC=CGIy7Pra4SR1VNitw$t7sF=01Gmd25i&YIWvB&(^n2P=gy2VuQ=wqnJWclDa*@B33>N_ zm;s|Oiozw+K~CmH1?dL&o?d{7tdQrkaz<1fGS4Y|$|3b;B@s*Hkj}Yv(sV462=EG% zO38dK$0Y{8?1+@FV*hhJi!{1p*c&y2nO869)a4W_J3{L| zTa5^@r0|tBeiT@B1nf=rpOHjAucW0E`hK6>{tZlaRfZ1}n0!u|Y`%)Xnl+J3Z&u(P z5;l>XPbyfqdfaH*3#kjR4wsnkt*j&TmmfF6*|`OYQ!3O;FP->u0;ZA^cLXn{!SlW1 zkid)YtoF^sG(T5l#O}Pn$x&XAk{NvNbv2(JfUyC5p6H8lznKc#RK2XCpZVLXXBS~| zkW2P(-F%CbJQFd2_8Zj5j>hVJJD}l zmVL07up}uJ`=MC}xo2Sc5k;=dfA3LwDJ%=qu!$;U1A6W<9B#WK65-`cWldf&#TQZi z>2guM*g;zEOK5A#chM2Go$ zwd_Mba6Ye*9p?DevTMz6|5USt>RV7P`c-@1@%dy4Lsz0&;!^)VJ|_De$!>86ntyWo z9s_6?nICUwyw148W_4Q{7#Cx3m>Je(FqqK88yN|f{T&jvRO@^UVdQ*dxn<+j!=`2%sDBtDlRKWmzoibHgbDA-$* zem<8T;)$QTmXM4BNetDkUk6@>ASZxn0N0e9U;qG)32*@i`4wsxX<|H43}mnr|00!{-R;k@8xi2xjOhz@hL(djy z(efstNEi*&4!{Q+j3zgyEU1;ah(GG_fK$89rr=g(%Q8pz}?cO(|ElH##50FA6%}OcF{cwYt60 z`Jh^$p!3a#;%F3H?|h@EA!L2oFF_|fMaImVI!$C>Gc0E7t}}PUjjQcugWX~@uSRPt zlUEY*C^>$#SrEMDhSkiZh8!uOkxNR-ZCny^PEO&_2iUh+2E*GF-5BeYL+$!rtJ~eY zIg>FdR*MB#N?(^hrcXC_mco4nW1camoW?C78|FAX9@f~McG-L6fVIuNS&kL!r9eXn zvs^yD*PiyYB@8{AoBJZ^rk;+jfXxp22pmah3SpC5>5AALiKeKKZ1QCjVt7l7!58W< z1cZdA=t!8O`7V#U#7DadSVrjzF`Y0lB(NJprf4qV@)c4sOHXHq&l2sm=Tb4F&|&a1 z&OR*Uu(x%Zec5DFs1S}>L@s6#7&4q?JI&!jvYTm^jm7pn)6^BtdX3FuSA%o2s0aPm z0-Fp1&@uwRT&6E!i*Y&57e z#?NL#xP1BF;fCg8-Q9R4uB)Cg769Y0tqBvH+1CexmqeAzt0fym((3o9j!2O!G6?sQ z0-ltBZ>bGQB6XU6xl0;Iq~t-r?TurNgc5xG2x(1+8_9bbe_WR$gzj%73br+?E%G)l zCIm#@isdeT^Opgy2mt(fMvLiCZUVp{07z;yKSLmd9||ZzDad|kV%1g(px`o2ZPY+? z0BwOs_e^P(mWF?LkW!F6Kq*Ue?G;Q2b~I64!Gwn)&6-y*K{qW?u3*B};mHUXkqKJ` zXpfHOVy)dk2JAfPwqHEZcIcM(+)r3m6pDAeJO~R1d{b@6Z&IF*9;?9+0D7`8k9Kk7 zC}7XP03qnZJTOpuPWZFzmAKXJfSfF*fdcI=NI6PVv9`n^OTm9hbW!y%B|y6_A_VFd z0|!G}M9|IPzf!6A|GQ~&E@nJ4f6j5!m(i< diff --git a/Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/4/8L/X3PMRJRGAACIU1WG1P1KJ0.uasset b/Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/4/8L/X3PMRJRGAACIU1WG1P1KJ0.uasset deleted file mode 100644 index 2552681ede7cd4fe98b38bf630e551f287bc0288..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10597 zcmb_i3vg8B75;^Y;1dzS7s?wHA)8G$52uz@@w2}vt)H+xA|HoM{O&CB`-6&-wx z%0LzSuwn;sM5owxDvnha6a{NX(c-AJYSq#~>S*zWilyiKA9wF=c1cvu%-!?<=bZ1H z^PltI|K8nX=Z%-$_vNu;$F3UA*r~%9`bQqrd;Y^S{z%^^byXkuPi@7+GynAX z(|sgcU>tp^-@DE8&e(P96B8$lpGGqG@?XC0uY79iLywGE(>Ff+&J`s4sD0Z92i+S! zJMGPa2V773=gcHod+W%5FIzliW#@%uYt}8FK9166wRGi|2RpRYJ%yIh_?eL-V+K~2G; z3LA4e84Hl(1}Yrrj**N_BJTARd;RWWm%XgmUuLuUy)KvER#IShc)dks#oi**cm$0; zJ(aODi9bVN(E2k{91hidE6H(LX~fa0O*4Leoon5c4_pWBw@x^Tl<%*r2~TzQ(3EDnaWDBCh#Z$bH7ZEfXRyn`M3_N|GisY};`eKmT# zQ`3z;7TETF1Zxd>;xR*ygt`oEX}mI+NU-{^y-!>SO}Q4+^q`?tMs%H`hduFVG?IwK zW32L$1D_y7XkjEA)?)1X7XzoF8=*i#fx_(Yb9KicbjRaSGwkTRZMWueVJ}-=65fvn zx%I9@JDchnXDzO9n9bQd|0NWNOWg5r-xAH}>SUK0E%Ufvz}#JtXjs$P4Q$lS zrmZL5(HW0vF@tSyYO6P$F74~o*puGB9_8phLo>5FP&GSw3Ptj0(P$vd{`=XX8*rbP z3--l=p{N$7Ep+&?bF7M`dazaVM}uultC*yn&Q-B)t=AXTIudM~b5iqo$d>wB>J!?M zNJnQ>b5Se7d)dQ(+nPY{e0+gCySv?sHlZuN0ikcWbHltT*)((K)^=%npDPxYJB3|v z`|1POK`AGxH0M6Fk#`OWy5qg9JFl!2o5LTC2Mx0x-)wpN+mlJkx0t8q>vq&I5DRO) z-k=d=TQFS{rQ}zWF}C;-@oUx`?)!tP9)xanJ$pRdfQe zvI(VcpdXl+@qc!BWCgdNF0#*t?>+;b#cgBSxc>f!57CS29@?e+cv_|a@qokYTJD=b%9>#7%3zzeINlnJMy?4Ow6CF0bW%sz7^8135~%G88tj(!4=1o4 z=)fSeZVrJtTU&GP<)bZ%CFDvg^s?uCjz`egIyyM3SFA{A2Ag-FW((?E7mpjM?K$@G z;4emTslgLq?z?)Q$Bm^t1wM8yNBNc*y1DRqJN2ok2g$lxQw^}OUmZo8sMoQ?in!jv zMz0P&12C5$bxQe)>Z+5m(;MgnitFsbi}J$Aw1Lh9+1wp%d;d6@MB)K)oTmY+IIJH* zUDV-LAJes9)E8!#=iM{XdW1D=U5P5q=!xqqSx5ctb^MMr@8?l}zOxTiE;Vx>)O`v-0Z8*KNS8G9IScD|0ulK=tL;V2Sdz=OVKjO;1D;1|28@b=|PZ zF9KB)kM-8?ZI)K2*x%%6LR z{(M8Q`<(KdKLoqa$gVt#ovMpV>OLhqZx%Z#VjeXhjt4p@+j^~bD#l=TWy0N{{^n+} zQ!)Hfhu2kS7CVJiwLo1KWU>1n$)%X##SMD^F>o8ah6gnfUTSiY$_ILKW%|mre_Hzb z)?zb)jMUy7Nzc}QNKoUrOti=e(xJxNFI{|{5f6I^>%{u-*Z~#c8J&ZM8V{#9=x9bf z+yg4!mpEyOTqiKzHQ-4x6o{#9qQs zR`u+&JmNAuvEgKf&hwXc@rs`10m0wU@l3ANj39r@+C=-t?IH>+Y*^9Qq1V>uca-@3 zi&jM-93M+J%3vu1S=4B1n)Y=;oA%AmsXkmlVT^R=kQHo~?2nJTLI{F~Df+upr)kc&Ww4cd@#MT&*TcHxYDb6F0SkpS9a!nA`@gSz$7UH z+klU$bfGsWxvB&nYibM*9jXe#_!PQ;fgLz(Foqm@$Pg45g@qmj8SF5J4aSgRtbF53 z9LU;kyzPTjUSdYS)zCmT%j{HAD9yQ3&P{$&k&vWCP_oPjhhS^V0+=ObnShBTPwgLR zl+Hd`;E)3A8guPPd=tv)5Cs5zM@OG55U>=VE(C0jmKD2w@ExUI{7H;c*WMbaFSA;M1qB#Q=l6bA~fp-DW03}v*Iahtxw!QRXQsM+# z`;2q;_i~gBIXTYRDL89#S{KW8X@ax~SLgm{GJOi0Cg;DtS#$DT4{A=_#Ur?|e}~Yi z&wjZd%{l40tY1lJJ^%xMqV0Qj(Q9hv<(4vV>lk)QX5~t*ICL2Aa#EaudBev*LU-{3 ztO$2cY7Bd|);(@)6WH@T0~OIQx|BV5I$mL6xyOyQ!(MHDNO79x#=}C@agQ7IgFUz+ zO9Jo07u5DVZMIlsnVclDg*+*W$;i{G`hvrTp*TpZ=FUst(W#5_;3mHPVmdsrAm>m!aU;MtL^h+vJ*X;q5D)7_qN$|9MRerlH&4MiRBE6tgO!1)op7|>O>-`K8H7F6e~htG>xiQeKJkO*)JQGo{dOQS$$%nnr?B zdng!gEh?SeT4GxqHy+h&JDVTZld9tsy0*qHk7E2{_o!j4OAV#@9R57Tw6KL7v# diff --git a/Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/8/L1/C6M65Q91PG5E7ML91VQZX3.uasset b/Content/__ExternalActors__/ZMMO/Maps/World/L_TestWorld/8/L1/C6M65Q91PG5E7ML91VQZX3.uasset deleted file mode 100644 index 059ff5ab3a4fa01683ff1c6b4bd062109cc2d208..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10911 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 L^7h%HE+GFulJ_zh diff --git a/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp b/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp index 574cb68..eb1ed6d 100644 --- a/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp +++ b/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp @@ -222,13 +222,33 @@ void AZeusPlayerProxy::SetZeusIdentity(const int64 InEntityId, const EZeusEntity void AZeusPlayerProxy::ApplyEntitySnapshot(const FZeusEntitySnapshot& Snapshot) { - // Estabiliza o offset de relogio na primeira amostra. Subsequente nao - // re-sincroniza para evitar saltos visuais; um esquema mais sofisticado - // (ema do offset) cabe quando tivermos ping/RTT estabilizado por sessao. + // 2026-06-06 H3 fix — disciplina o ServerClockOffsetMs via EMA com clamp. + // Antes: offset congelado na 1a amostra. Em cross-server handoff a fonte + // do ServerTimeMs muda (publisher antigo -> novo) e o offset fica + // permanentemente skewed, alimentando tremor pos-handoff e drift cumulativo + // de clock cliente vs server. Agora cada amostra recalcula candidato e + // suaviza (alpha=0.02, ~50 amostras pra convergir) com clamp max 2ms/snapshot + // pra evitar salto visual durante transicao. + const int64 LocalNowMs = static_cast(FPlatformTime::Seconds() * 1000.0); + const int64 OffsetCandidate = LocalNowMs - Snapshot.ServerTimeMs; if (SnapshotBuffer.Num() == 0) { - const int64 LocalNowMs = static_cast(FPlatformTime::Seconds() * 1000.0); - ServerClockOffsetMs = LocalNowMs - Snapshot.ServerTimeMs; + // Primeira amostra: bootstrap direto (nao tem baseline pra suavizar). + ServerClockOffsetMs = OffsetCandidate; + } + else + { + const double Alpha = 0.02; + const int64 RawTarget = static_cast( + static_cast(ServerClockOffsetMs) * (1.0 - Alpha) + + static_cast(OffsetCandidate) * Alpha); + // Clamp max 2ms por amostra — em 30Hz isso converge a 60ms/s, mais que + // o suficiente pra absorver drift normal sem causar tremor visual. + const int64 Delta = RawTarget - ServerClockOffsetMs; + const int64 ClampedDelta = (Delta > 2) ? 2 + : (Delta < -2) ? -2 + : Delta; + ServerClockOffsetMs += ClampedDelta; } FZeusProxySnapshot S;