Quick Flow
클라이언트 예측(client prediction)은 내 입력의 결과를 화면에서 먼저 계산하는 방식이고, 서버 보정(reconciliation)은 서버 snapshot을 받은 뒤 그 확정 상태 위에 아직 승인되지 않은 입력을 다시 적용하는 절차입니다. 화면을 빠르게 보이게 하되 서버가 최종 상태를 정하는 계약입니다.
client: input #42를 저장하고 로컬 state에 즉시 적용 -> server로 전송
server: #42를 검증·simulation 후 authoritative state와 lastProcessedInput=42 전송
client: server state로 되돌림 -> #43 이후의 미승인 input을 순서대로 다시 적용void ApplyLocalSnapshot(Snapshot snapshot)
{
localState = snapshot.MyAuthoritativeState;
pendingInputs.RemoveAll(input => input.Sequence <= snapshot.LastProcessedInput);
foreach (var input in pendingInputs)
{
Simulate(ref localState, input);
}
}예측에 필요한 세 가지
첫째, 각 input에는 session 안에서 증가하는 sequence가 필요합니다. 서버 snapshot은 어느 sequence까지 처리했는지 알려야 하며, 클라이언트는 그보다 뒤인 input만 재실행합니다. timestamp만으로는 같은 frame에 여러 input이 들어오거나 시계가 다른 상황에서 승인 경계를 명확히 하기 어렵습니다.
둘째, client와 server가 충분히 같은 simulation rule을 적용해야 합니다. 이동 속도, 가속, jump 가능 여부, collision query, fixed delta가 다르면 모든 snapshot이 보정처럼 보입니다. 완전히 같은 physics engine을 두기 어렵다면, 예측 범위를 단순한 이동처럼 제어 가능한 부분으로 좁히고 서버 보정을 더 자주 받아야 합니다.
셋째, 서버 snapshot에는 내 플레이어의 확정 state와 승인된 input sequence가 함께 있어야 합니다. 다른 플레이어 state는 보통 내가 재실행할 수 없으므로 snapshot 보간으로 표시합니다. 내 캐릭터와 다른 entity에 같은 동기화 방식을 강요하지 않습니다.
무엇을 예측할까
즉시 반응하지 않으면 조작감이 크게 나빠지는 local movement, aim, 자기 캐릭터의 단순한 cooldown 표시는 예측 후보입니다. 반면 다른 플레이어의 입력, server RNG가 정하는 loot, 숨겨진 정보, 복잡한 물리 상호작용까지 예측하면 오류 원인과 보정 폭이 커집니다.
발사 연출도 client가 즉시 보여 줄 수 있지만, 피해와 탄약의 최종 변화는 server event를 기다립니다. 예측한 탄환 효과가 server 거부 뒤 취소될 수 있다는 UI 정책을 정해야 합니다. 예측을 했다는 이유로 server validation을 빼면 권위 서버의 경계가 무너집니다.
보정이 튀는 이유
보정은 client와 server가 같은 input을 서로 다른 시작 state 또는 다른 rule로 계산했다는 신호입니다. packet loss만이 원인은 아닙니다. 늦게 처리된 input, server collision과 client collision의 차이, entity가 막고 있는 위치를 client가 몰랐던 경우, fixed step 불일치가 모두 원인이 됩니다.
오차를 화면에서 부드럽게 감추는 것은 가능하지만, state 자체를 조금씩 틀린 값으로 유지하면 다음 판정이 더 어긋납니다. 먼저 authoritative state로 논리 값을 맞추고, 화면 위치만 짧게 smoothing합니다. 오차 크기·보정 횟수·재실행 input 수를 telemetry로 남기면 어떤 rule이 예측에 부적합한지 찾을 수 있습니다.
server snapshot을 받았을 때 현재 화면 위치에 조금 더하는 방식은 보정이 아닙니다. 확정 state로 복원한 뒤, 아직 server가 처리하지 않은 input만 다시 적용해야 승인 경계가 유지됩니다.
참고 링크
2 sources