@@ -58,6 +58,11 @@ const MAX_REACH: f32 = 6.0;
5858/// Ping 间隔(毫秒)。
5959const PING_INTERVAL_MS : f64 = 5000.0 ;
6060
61+ /// Pong 校时样本权重。Pong 带 RTT 信息,可信度高于裸 PlayerTick。
62+ const CLOCK_PONG_ALPHA : f64 = 0.2 ;
63+ /// PlayerTick 校时样本权重。它可能受单向排队延迟影响,只作为低权重补充。
64+ const CLOCK_TICK_ALPHA : f64 = 0.05 ;
65+
6166/// 信令服务 URL meta tag 名称。
6267const SIGNALING_META_NAME : & str = "signaling-url" ;
6368
@@ -1846,7 +1851,10 @@ fn render_game_frame(
18461851 return Ok ( ( ) ) ;
18471852 } ;
18481853 let now = now_ms ( ) ;
1849- if game. entity_id != 0 && now - game. last_ping_sent_ms >= PING_INTERVAL_MS {
1854+ if game. mode != GameMode :: Local
1855+ && game. entity_id != 0
1856+ && now - game. last_ping_sent_ms >= PING_INTERVAL_MS
1857+ {
18501858 let client_time_ms = now as u64 ;
18511859 game. pending_pings . insert ( client_time_ms, now) ;
18521860 // 上限 16 个待办,避免长期不通时无限增长
@@ -2114,7 +2122,7 @@ fn render_game_frame(
21142122 let mut view_proj_for_np = glam:: Mat4 :: IDENTITY ;
21152123 if let Some ( g) = a. game . as_mut ( ) {
21162124 view_proj_for_np = g. camera . vp_matrix ( ) ;
2117- let render_target = now_local + g. server_clock_offset_ms as f64 - g. interp . delay_ms ;
2125+ let render_target = now_local + g. server_clock_offset_ms - g. interp . delay_ms ;
21182126 let cam_pos = g. camera . position ;
21192127 let eids: Vec < EntityId > = g. interp . ids ( ) . collect ( ) ;
21202128 for eid in eids {
@@ -2322,8 +2330,7 @@ fn render_game_frame(
23222330 let now = now_ms ( ) ;
23232331 let mut instances: Vec < voxweb_render:: passes:: player:: PlayerInstance > = Vec :: new ( ) ;
23242332 if let Some ( ref mut game) = a. game {
2325- let render_server_time =
2326- now + game. server_clock_offset_ms as f64 - game. interp . delay_ms ;
2333+ let render_server_time = now + game. server_clock_offset_ms - game. interp . delay_ms ;
23272334 let eids: Vec < voxweb_core:: protocol:: EntityId > = game. interp . ids ( ) . collect ( ) ;
23282335 for eid in eids {
23292336 if let Some ( ( pos, _yaw, _pitch) ) = game. interp . advance ( eid, render_server_time)
@@ -2509,9 +2516,16 @@ fn dispatch_actions(game: &mut Game, input: &InputState) {
25092516 let server = game. server . borrow ( ) ;
25102517 server. world . get_block ( pos)
25112518 } ;
2512- // Local 模式 client 和 server 共享同一份 world,乐观更新会干扰 server 校验
2513- // (server 读回 AIR 误判 BlockNotEmpty 拒绝)。因此跳过乐观 set_block;
2514- // BlockUpdate 返回后再重 mesh。Phase 5 Remote 端加独立 WorldView 时再加乐观路径。
2519+ let input_tick = game. local_input_tick ;
2520+ let player_position = game. physics . feet_position ;
2521+ // Remote 的 server.world 只是本地世界视图,可以安全乐观修改;
2522+ // Local/Host 与权威 server 共享同一份 world,仍等 BlockUpdate,避免提前改世界干扰校验。
2523+ if game. mode == GameMode :: Remote {
2524+ game. server . borrow_mut ( ) . world . set_block ( pos, BlockID :: AIR ) ;
2525+ for cp in affected_chunks ( pos) {
2526+ game. mesh_jobs . enqueue ( cp, MeshPriority :: High ) ;
2527+ }
2528+ }
25152529 let request_id = game. pending . next_request_id ( ) ;
25162530 game. pending . insert (
25172531 request_id,
@@ -2521,8 +2535,12 @@ fn dispatch_actions(game: &mut Game, input: &InputState) {
25212535 backup,
25222536 } ,
25232537 ) ;
2524- game. net
2525- . send_client_message ( ClientMessage :: Break { pos, request_id } ) ;
2538+ game. net . send_client_message ( ClientMessage :: Break {
2539+ pos,
2540+ request_id,
2541+ input_tick,
2542+ player_position,
2543+ } ) ;
25262544 game. last_break_at_ms = now;
25272545 }
25282546
@@ -2551,6 +2569,8 @@ fn dispatch_actions(game: &mut Game, input: &InputState) {
25512569 return ;
25522570 }
25532571 let request_id = game. pending . next_request_id ( ) ;
2572+ let input_tick = game. local_input_tick ;
2573+ let player_position = game. physics . feet_position ;
25542574 game. pending . insert (
25552575 request_id,
25562576 PendingAction {
@@ -2559,10 +2579,18 @@ fn dispatch_actions(game: &mut Game, input: &InputState) {
25592579 backup,
25602580 } ,
25612581 ) ;
2582+ if game. mode == GameMode :: Remote {
2583+ game. server . borrow_mut ( ) . world . set_block ( neighbor, block) ;
2584+ for cp in affected_chunks ( neighbor) {
2585+ game. mesh_jobs . enqueue ( cp, MeshPriority :: High ) ;
2586+ }
2587+ }
25622588 game. net . send_client_message ( ClientMessage :: Place {
25632589 pos : neighbor,
25642590 block,
25652591 request_id,
2592+ input_tick,
2593+ player_position,
25662594 } ) ;
25672595 }
25682596}
@@ -2581,6 +2609,21 @@ fn send_chat(game: &mut Game, content: String) {
25812609 . send_client_message ( ClientMessage :: Chat { content } ) ;
25822610}
25832611
2612+ /// 摄入一条 Host 时钟偏移样本。
2613+ ///
2614+ /// 高延迟网络下,直接用最新 `server_time_ms - now` 覆盖偏移会让远端玩家插值 target
2615+ /// 来回抖动。这里第一条样本直接采用,后续做指数平滑;Pong 样本权重较高,
2616+ /// PlayerTick 样本只做低权重微调。
2617+ fn ingest_server_clock_sample ( game : & mut Game , estimated_offset_ms : f64 , alpha : f64 ) {
2618+ if !game. server_clock_synced {
2619+ game. server_clock_offset_ms = estimated_offset_ms;
2620+ game. server_clock_synced = true ;
2621+ return ;
2622+ }
2623+ let a = alpha. clamp ( 0.0 , 1.0 ) ;
2624+ game. server_clock_offset_ms = game. server_clock_offset_ms * ( 1.0 - a) + estimated_offset_ms * a;
2625+ }
2626+
25842627fn apply_server_message ( game : & mut Game , msg : ServerMessage ) {
25852628 match msg {
25862629 ServerMessage :: Welcome {
@@ -2678,8 +2721,12 @@ fn apply_server_message(game: &mut Game, msg: ServerMessage) {
26782721 players,
26792722 server_time_ms,
26802723 } => {
2681- let now = ( now_ms ( ) ) as i64 ;
2682- game. server_clock_offset_ms = server_time_ms as i64 - now;
2724+ let now = now_ms ( ) ;
2725+ if game. mode != GameMode :: Local {
2726+ let one_way_ms = game. rtt_ms . map ( |rtt| rtt as f64 * 0.5 ) . unwrap_or ( 0.0 ) ;
2727+ let estimated_offset = server_time_ms as f64 + one_way_ms - now;
2728+ ingest_server_clock_sample ( game, estimated_offset, CLOCK_TICK_ALPHA ) ;
2729+ }
26832730
26842731 for snap in & players {
26852732 if snap. entity_id == game. entity_id {
@@ -2750,11 +2797,17 @@ fn apply_server_message(game: &mut Game, msg: ServerMessage) {
27502797 }
27512798 ServerMessage :: Pong {
27522799 client_time_ms,
2753- server_time_ms : _ ,
2800+ server_time_ms,
27542801 } => {
27552802 if let Some ( sent_ms) = game. pending_pings . remove ( & client_time_ms) {
2756- let rtt = ( now_ms ( ) - sent_ms) as f32 ;
2757- game. rtt_ms = Some ( rtt) ;
2803+ let now = now_ms ( ) ;
2804+ let rtt = ( now - sent_ms) as f32 ;
2805+ game. rtt_ms = Some ( match game. rtt_ms {
2806+ Some ( prev) => prev * 0.8 + rtt * 0.2 ,
2807+ None => rtt,
2808+ } ) ;
2809+ let estimated_offset = server_time_ms as f64 + ( rtt as f64 * 0.5 ) - now;
2810+ ingest_server_clock_sample ( game, estimated_offset, CLOCK_PONG_ALPHA ) ;
27582811 }
27592812 }
27602813 }
0 commit comments