Quick Flow
handoff의 목표는 source와 target이 player를 동시에 authoritative하게 처리하지 않는 것입니다. source는 이동 조건을 확정한 revision의 handoff state와 짧은 만료 시간을 가진 token을 만들고, target은 token으로 handoff를 claim해 player를 준비합니다. target 준비 확인 뒤에만 source가 ownership을 해제합니다.
source: 이동 rule 검증 -> handoff {player, revision, state, token} 생성
target: token·expiry·revision 검증 -> target state 준비 -> accepted 응답
source: accepted 확인 -> source entity despawn·ownership 해제
client: target endpoint 접속 -> join snapshot 수신client가 새 endpoint를 먼저 안다고 해서 handoff가 끝난 것이 아닙니다. client reconnect, network timeout, source crash는 언제든 중간에 일어날 수 있으므로, source와 target이 어떤 상태까지 합의했는지를 control plane 또는 durable handoff record에서 조회할 수 있어야 합니다.
handoff record에 넣을 것
record에는 handoff ID, player 또는 entity ID, source owner, target owner, source state revision, 생성·만료 시각, 상태(PREPARED, ACCEPTED, COMMITTED, CANCELLED)를 둡니다. token은 player identity와 target owner에 묶고, target 복원이 끝난 뒤 재사용되지 않게 표시합니다. target이 같은 token을 두 번 받으면 이미 만든 결과를 반환하고 entity를 다시 spawn하지 않습니다.
function acceptHandoff(token: HandoffToken) {
const handoff = handoffStore.claim(token.id, token.targetOwner);
if (!handoff || handoff.expiresAt < now()) return reject("handoff-expired");
if (!canLoadRevision(handoff.stateRevision)) return reject("revision-mismatch");
const entity = restoreEntity(handoff.state);
handoffStore.markAccepted(handoff.id, entity.id);
return entity;
}source가 PREPARED 뒤 crash하면 target은 record 상태와 lease를 보고 timeout 뒤 cancel 또는 recovery로 넘깁니다. target이 ACCEPTED 뒤 crash하면 source가 ownership을 아직 놓지 않았으므로 계속 simulation하거나 handoff를 취소할 수 있습니다. COMMITTED는 source가 더 이상 input을 받지 않는 지점입니다.
state를 어디까지 보낼까
handoff state는 target이 player를 재구성하는 최소 authoritative state입니다. position만 보내고 quest phase, cooldown, inventory revision을 빠뜨리면 target이 오래된 persistence를 섞어 잘못된 player를 만들 수 있습니다. 반대로 모든 connection buffer와 UI hint까지 복사하면 data가 커지고 transport에 묶입니다. game rule state, ownership, 필요한 cooldown·sequence를 명시적으로 versioned schema로 만듭니다.
영속 profile을 transaction으로 갱신하는 일과 live world handoff를 하나의 database transaction으로 묶으려 하면 long-running simulation을 DB lock에 매달게 됩니다. durable record는 ownership 결정을 복구할 수 있게 하고, 결과·보상처럼 영속적인 변화는 별도 멱등 key로 처리합니다.
자주 틀리는 부분
target이 client connect만 보고 player를 active로 만들면, source에도 아직 남은 player가 새 input을 보낼 수 있습니다. ACCEPTED와 COMMITTED를 구분하고 source가 despawn한 뒤 target이 입력을 활성화할 시점을 정합니다.
또한 handoff token을 bearer string 하나로만 두고 scope와 expiry를 빼면 다른 match·다른 target에서 replay될 수 있습니다. token 검증은 인증된 player와 target ownership 확인을 함께 해야 합니다.
참고 링크
2 sources