> For the complete documentation index, see [llms.txt](/ko/llms.txt)


# 오브젝트 동기화의 구조

sync와 stateMachine 룸에서 오브젝트가 어떻게 동기화되고, 누가 무엇을 변경할 수 있는지



`sync`와 `stateMachine` 룸에서 오브젝트가 어떻게 동기화되고, 누가 무엇을 변경할 수 있는지를 설명합니다. 이 페이지의 내용은 게임 엔진과 관계없이 공통입니다. 엔진별 설정과 API는 [Unity 클라이언트]()와 [Godot 클라이언트]()를 참조해 주세요.

## 전체 구조 {#overview}

| 용어 | 의미 | 적이 10체 있는 경우의 예 |
| --- | --- | --- |
| 공유 오브젝트 | 룸이 만들고, 참가자 전원이 같은 상태를 참조하는 대상 | 10체의 적 |
| 플레이어 오브젝트 | 플레이어가 생성하고 소유하는 오브젝트. 소유자가 값을 변경한다 | 각 플레이어의 캐릭터 |
| 오브젝트 ID | 룸 내의 하나의 실체를 나타내는 ID. 서버가 발행한다 | 적마다 다른 10개의 ID |
| Prefab ID | 오브젝트의 종류를 나타내는 ID. 클라이언트에서 표시할 Prefab(Godot에서는 씬)을 선택하는 데 사용한다 | 10체 모두 같은 ID |
| 동기화 프로퍼티(필드) | 참가자에게 배포하는 값 | 위치, HP |

오브젝트의 종류에 따라 값을 변경할 수 있는 사람이 정해집니다.

| 종류 | 값을 변경하는 사람 | 예 |
| --- | --- | --- |
| 플레이어 오브젝트 | 소유자 | 각 플레이어의 캐릭터 |
| 조작 권한을 대여하는 공유 오브젝트 | 조작 권한을 가진 참가자([조작 권한](#authority)) | 들고 옮기는 상자, 패들 |
| 서버 관리 공유 오브젝트 | 룰만. 참가자는 [조작을 전송한다](#interaction) | 적, 문 |
| Simulated 오브젝트 | 룰만. 소유자는 입력을 보낸다([틱](#tick)) | 입력으로 움직이는 캐릭터 |

데이터는 다음 순서로 도착합니다.

1. 룸에 참가하면 그 시점의 모든 오브젝트와 값(스냅샷)이 도착하고, 클라이언트가 표시용 인스턴스를 생성합니다.
2. 이후에는 변경된 값만 도착하여 표시가 갱신됩니다.

## 전송 순서 {#ordering}

RPC·생성·이벤트·프로퍼티 등의 전송은 접속 후라면 언제 호출해도 됩니다. 스냅샷 반영 전이나 생성 알림 안에서 호출한 것은 보낼 수 있게 된 시점에 호출한 순서대로 전송됩니다. 전송 순서는 모든 오브젝트와 룸 전체의 조작에 걸쳐 유지됩니다.

수신한 메시지에는 룸 서버의 시각이 붙어 있습니다. 클라이언트는 이를 사용하여 참가자에게 공통인 시각을 가지며, 다른 플레이어의 오브젝트를 수신 시각에 맞춰 부드럽게 움직입니다.

## 위치·회전 {#transform}

위치·회전은 표준 필드가 있어, 동기화 스키마가 없어도 동기화할 수 있습니다. 서버 측에서 동기화를 허가한 축만 클라이언트에서 선택합니다.

| 서버의 선언 | 동기화할 수 있는 축 | 단위 간격(표준) |
| --- | --- | --- |
| `RoomSync.Position` | 위치 X / Y / Z | 0.01(게임의 거리 단위) |
| `RoomSync.Rotation` | 회전 X / Y / Z | 360 / 4096도 |
| `RoomSync.Transform` | 위의 양쪽 모두 | 위와 동일 |

- 위치는 월드 좌표, 회전은 오일러 각으로 다룹니다. Scale은 동기화하지 않습니다.
- 다른 클라이언트에서는 조금 과거(기본값 0.1초 전)의 자세를, 전후로 수신한 갱신 사이에서 보간하여 표시합니다. 일정 거리보다 떨어진 위치로는 보간하지 않고 이동시킬 수도 있습니다(리스폰 등).
- 갱신이 멈추면 마지막으로 수신한 자세에서 멈춥니다. 앞으로의 위치를 예측하지는 않습니다.
- 값은 단위 간격으로 반올림하여 전송합니다. 단위 간격보다 작은 흔들림은 전송되지 않습니다.

## 독자적인 값 {#fields}

HP나 개폐 상태 등의 독자적인 값은 룰에서 동기화 스키마로 선언하고, 생성된 `.realtimeschema`를 클라이언트에서 사용합니다. 선언 작성 방법은 [동기화 스키마](#schema)를 참조해 주세요.

- 쓰는 측이 값을 읽으면 자신이 마지막으로 쓴 값(전송 전이라도)이 반환됩니다. 예를 들어 HP에 1을 더하는 쓰기를 전송 사이에 2번 하면 2가 증가합니다.
- 룸에서 수신한 값만 읽는 방법(confirmed)도 있습니다.
- 수신한 값을 표시에 전달하는 구조(Unity의 Property bindings, Godot의 `property_bindings`)는 표시를 바꿀 뿐입니다. 서버의 값은 바뀌지 않습니다. 서버가 관리하는 값은 [조작을 전송하는](#interaction) 것으로 변경합니다.

## 조작 권한 {#authority}

서버가 「조작 권한을 대여한다」고 선언한 오브젝트는 조작 권한을 얻은 참가자만 값을 전송할 수 있습니다. 상자를 들고 옮기거나, 패들을 움직이는 등의 용도에 사용합니다.

- 취득 요청은 비동기로 처리되므로, 요청한 직후에 조작할 수 있다고는 할 수 없습니다.
- 다른 참가자가 조작 권한을 가지고 있을 때는 `LeaseBusy`로 거부됩니다. 거부된 경우에는 자동으로 재요청하지 않습니다.
- 조작 권한에는 기한이 있으며, 클라이언트는 기한이 만료되기 전에 자동으로 갱신할 수 있습니다.
- 조작할 수 있는 동안에만 이동이나 입력 처리를 활성화할 수 있습니다. 공유 오브젝트는 조작 권한이 있는 동안, 플레이어 오브젝트와 Simulated 오브젝트는 소유하고 있는 동안입니다.
- 조작 권한을 기다리는 요청의 결과는 다음 중 하나입니다.

| 결과 | 의미 |
| --- | --- |
| 성공 | 조작할 수 있게 되었다 |
| 거부 | `LeaseBusy`: 다른 참가자가 조작 권한을 가지고 있다. `Forbidden`: 조작 권한을 취득할 수 없는 오브젝트(서버 관리 오브젝트나 다른 플레이어의 오브젝트) |
| 철회 | 요청을 철회했다(조작 권한의 반환, 표시의 비활성화, 오브젝트의 삭제) |

- 표시용 인스턴스가 비활성화되면 조작 권한은 자동으로 반환됩니다.
- 조작 권한을 수동으로 취득·연장·해제하는 API(`Acquire`·`Renew`·`Release`)도 있습니다. 이는 룸의 응답을 그대로 반환할 뿐이며, 처리의 활성화나 자동 갱신은 하지 않습니다.
- 조작 권한 보유자가 바뀔 때마다(획득·반환·기한 만료) 새 기간이 시작됩니다. 이전 기간에 보낸 값(UDP로 늦게 도착한 값)은 반영되지 않으며 오류도 되지 않습니다.

## 서버에 조작 전송하기 {#interaction}

서버가 관리하는 오브젝트(적의 HP 등)는 클라이언트에서 값을 변경할 수 없습니다. 대신 「이 오브젝트에 method의 조작을 한다」는 요청(Interact)을 전송합니다. 요청은 룰의 `Interact`(GDScript에서는 `on_interact`)에 전달되고, 룰이 검증하여 값을 갱신합니다.

`method`는 룰에서 조작의 종류를 구분하기 위한 번호입니다. 룰이 [`[RoomAction]`](#actions) enum(GDScript에서는 `## @room_action`)으로 이름을 붙였다면, 생성된 상수를 그대로 전달할 수 있습니다. 룰로 동작하는 룸에는 추가 인수를 전송할 수 없습니다.

조작 요청(과 조작 권한의 취득·연장·해제)은 한 번만 완료됩니다. 완료 상태는 다음 중 하나입니다.

| 상태 | 의미 |
| --- | --- |
| 성공(Succeeded) | 수락되었다 |
| 거부(Refused) | 거부되었다. 사유는 결과 코드(아래 표) |
| 미전송(NotSent) | 전송할 수 없었다 |
| 철회(Abandoned) | 응답이 오지 않게 되었다(접속이 종료되었거나, 재접속으로 룸에 다시 들어갔다) |

대상을 없애는 조작(아이템 획득 등)도 룸이 오브젝트를 없앤 뒤 결과를 반환하므로 성공으로 완료됩니다.

| 결과 코드 | 값 | 의미 |
| --- | --- | --- |
| `Success` | 0 | 수리되어 변경이 전원에게 배포되었다 |
| `MissingObject` | 1 | 대상 오브젝트가 없다 |
| `Forbidden` | 2 | 이 오브젝트에는 허가되지 않은 조작 |
| `LeaseBusy` | 3 | 다른 참가자가 조작 권한을 가지고 있다 |
| `StaleEpoch` | 4 | 조작 권한 상태가 바뀐 후의 오래된 요청 |
| `InvalidOperation` | 5 | 현재 상태에서는 받아들이지 않는 조작, 또는 룰의 실행 오류 |
| `AlreadyApplied` | 6 | 룰이 이미 적용 완료로 반환했다. 클라이언트에서는 성공으로 취급한다 |
| `StaleRequest` | 7 | 같은 요청 ID가 이미 처리 완료 |
| 룰의 거부 이유 | 100 이상 | 룰이 `Step.Ignore`로 반환한 값 |

룰이 `Step.Ignore`로 거부한 경우에는 그 이유의 수치가 그대로 전달됩니다. 룸의 결과와 구별할 수 있도록 룰 고유의 거부 이유에는 100 이상의 값을 사용해 주세요([오류 enum](#checks)).

## 플레이어 오브젝트 {#player-objects}

플레이어가 조작하는 캐릭터 등, 플레이어가 소유하는 오브젝트는 클라이언트에서 생성을 요청하여 만듭니다. 생성 방법은 2가지입니다.

| 방법 | 동작 |
| --- | --- |
| 즉시 만들기(Unity의 `world.Instantiate`, Godot의 `world.instantiate`) | 인스턴스를 즉시 반환하며, 자신의 소유로 사용할 수 있다. 이동이나 RPC는 룸이 생성을 확정할 때까지 보류되며, 확정 후에 전송된다. 확정될 때까지 움직인 위치는 유지된다. 동기화 값은 확정 후에 사용할 수 있다 |
| 확정을 기다려 만들기(`world.Spawn` / `world.spawn`) | 룸의 확정을 기다린 후 인스턴스를 만든다 |

룸이 생성을 거부하면(소유 수의 상한 64 초과나, 오브젝트 카탈로그를 설정한 룸에서 등록되지 않은 Prefab) 거부가 통지되고, 즉시 만든 인스턴스는 삭제됩니다.

- 소유권은 다른 참가자에게 넘길 수 있습니다(플레이어 오브젝트만). 오브젝트 카탈로그에서 `"ownership": "takeover"`로 한 오브젝트는 누구나 소유권을 취득할 수 있습니다.
- 소유자가 퇴장하면 그 플레이어 오브젝트는 삭제됩니다. 오브젝트 카탈로그에서 `"keepOnLeave": true`로 한 오브젝트는 [마스터 클라이언트](#master)에게 인계됩니다.
- 소유자가 바뀔 때마다 소유 기간이 새로 시작됩니다. 이전 소유 기간에 보낸 값(UDP로 늦게 도착한 값)은 같은 참가자가 소유권을 되찾은 뒤에도 반영되지 않습니다.

## RPC {#rpc}

오브젝트의 메서드를 다른 클라이언트에서 호출합니다.

| 대상 | 전달되는 상대 |
| --- | --- |
| All | 호출한 플레이어를 포함한 전원 |
| Others | 호출한 플레이어 이외 |
| Owner | 오브젝트의 소유자 |
| AllBuffered / OthersBuffered | 위에 더해, 나중에 참가한 플레이어에게도 전달된다. 오브젝트 삭제나 호출한 플레이어의 퇴장으로 파기된다 |
| 1명 지정 | 지정한 접속 ID의 참가자 |

- RPC의 ID는 메서드 이름으로부터 정해집니다. 메서드 이름을 바꾸면 ID도 바뀌므로, 오브젝트 카탈로그와 클라이언트를 맞추어 갱신해 주세요.
- 누가 어떤 RPC를 호출할 수 있는지 제한하려면 [오브젝트 카탈로그](#catalog)를 설정합니다. 설정하면 `methods`에 등록한 ID는 소유자만 호출할 수 있게 되며, 다른 플레이어의 오브젝트에도 호출하는 RPC는 `publicMethods`에 등록합니다.
- 인수는 합계 4096바이트까지입니다. 마지막 인수로 호출자의 접속 ID 등을 받을 수 있습니다.
- 수신한 RPC는 도착한 순서대로, 수신 처리를 마친 후에 호출됩니다. RPC 안에서 RPC를 전송하거나 값을 변경할 수 있습니다.
- 공유 오브젝트에는 RPC를 전송할 수 없습니다. [조작 전송](#interaction)을 사용합니다.

## 룸 이벤트 {#events}

오브젝트에 연결되지 않은 알림은 룸 이벤트로 전송합니다. 참가자라면 누구나 전송할 수 있습니다. 대상은 All / Others와 각각의 Buffered, 또는 1명입니다. Buffered 이벤트는 전송한 플레이어가 퇴장하면 파기됩니다.

## 커스텀 프로퍼티 {#properties}

룸이나 플레이어에 붙이는 값입니다. 나중에 참가한 플레이어에게도 전달됩니다.

- 값으로 사용할 수 있는 타입은 RPC의 인수와 같습니다. `null`을 쓰면 삭제합니다.
- 쓰기는 룸을 경유하여 전원에게 도착한 시점에 반영됩니다. 쓴 플레이어의 로컬에서도 마찬가지이므로, 전원이 같은 순서로 값을 봅니다.
- 플레이어의 프로퍼티는 자신의 것만 쓸 수 있습니다. 플레이어가 퇴장하면 그 플레이어의 프로퍼티는 삭제됩니다.
- 키는 64바이트까지, 룸 전체에서 1024건까지입니다.

## 마스터 클라이언트 {#master}

룸은 참가자 중 1명을 마스터 클라이언트로 합니다. 처음 참가한 플레이어가 마스터가 되며, 마스터가 퇴장하면 남은 플레이어 중 가장 먼저 참가한 플레이어로 넘어갑니다. 마스터는 다른 참가자를 마스터로 지명할 수 있습니다.

게임 진행을 서버에서 결정하고 싶은 경우에는 마스터 클라이언트가 아니라 `stateMachine` 룰을 사용하면 클라이언트 개조의 영향을 받지 않습니다.

## Interest Group {#interest-groups}

오브젝트를 그룹으로 나누어, 구독하고 있는 플레이어에게만 갱신을 전달합니다. 그룹은 소유자가 정하며, 그룹 0은 전원에게 전달됩니다.

- 그룹으로 필터링되는 것은 값의 갱신과, 그 오브젝트에 대한 All / Others 대상 RPC입니다. 생성·삭제·소유자나 그룹의 변경은 전원에게 전달됩니다.
- 구독하면 그 그룹 오브젝트의 현재 값이 즉시 도착합니다.
- 구독은 재접속해도 유지됩니다. 1접속당 256그룹까지입니다.

## 틱과 예측 {#tick}

룸의 룰이 틱과 Simulated 오브젝트를 정의하는 경우, 그 종류의 오브젝트는 서버가 입력으로부터 움직입니다([틱](#tick)). 클라이언트는 위치를 쓰지 않고 입력을 보냅니다.

- 입력은 32개 정수 슬롯의 "현재 상태"입니다. 룸의 틱마다 전송됩니다. 범위를 지정한 슬롯은 룸이 범위 안으로 제한해서 읽습니다.
- 자신이 소유한 Simulated 오브젝트가 있는 동안에만 보냅니다. 1회 전송에 직전 3틱 분량의 입력을 넣으므로, UDP에서 하나가 유실되어도 보완할 수 있습니다.
- 클라이언트는 입력이 룸의 처리보다 조금(기본 2틱) 먼저 도착하도록, 입력에 붙이는 틱 번호를 룸보다 앞서 진행시킵니다. 룸에서 오는 시각을 보고 진행 속도를 ±10% 이내에서 자동 조정합니다.
- Simulated 오브젝트는 소유자라도 값을 쓰거나 삭제할 수 없습니다. 삭제는 룰의 `Despawn`이나 소유자의 퇴장으로 이루어집니다.

룰에 예측 처리([예측](#predict))가 있으면 컴파일러가 클라이언트용 예측 코드를 출력합니다. 등록하면 다음과 같이 동작합니다.

- 자신이 소유한 Simulated 오브젝트는 입력한 틱의 결과를 바로 표시합니다. 왕복 통신 시간을 기다리지 않습니다.
- 룸의 계산 결과가 도착할 때마다 그 결과에서 현재 틱까지 다시 계산합니다(롤백). 예측이 빗나갔을 경우에는 차이를 약 0.1초에 걸쳐 눈에 띄지 않게 좁힙니다.
- 다른 플레이어의 캐릭터는 룸의 결과를 보간하여 표시합니다.
- 예측 안에서 생성된 오브젝트(예측 스폰)는 먼저 표시되며, 룸의 오브젝트가 도착하면 같은 인스턴스로 인계됩니다. 예측으로 삭제된 오브젝트는 룸이 삭제할 때까지 그려지지 않습니다.
- 입력에는 그때 다른 오브젝트가 화면에서 얼마나 늦게 표시되고 있었는지가 붙습니다. 룸은 이것을 사용하여 쏜 쪽의 화면대로 판정합니다([지연 보상](#lag-compensation)). 정확하게 계산하려면 클라이언트에 서버 시각을 전달하고, 표시 지연(View Delay)을 위치 보간 지연과 같은 값(기본 0.1초)으로 합니다.

## UDP {#udp}

위치 등의 정기적인 갱신을 UDP로 전송하면 통신이 막혔을 때의 지연을 줄일 수 있습니다. 생성·RPC·이벤트·프로퍼티 등 도착하지 않으면 곤란한 것은 WebSocket 그대로입니다.

- 클라이언트는 룸으로의 등록과 등록 유지를 수행합니다. 룸으로부터 응답이 있으면 UDP를 사용하고, 10초간 응답이 없으면 WebSocket으로 돌아갑니다.
- UDP로 전송한 값은 룸에서 보낸 값이든 클라이언트에서 보낸 값이든 누락에 대비하여 1초마다 WebSocket으로 재전송됩니다. UDP가 한동안 도착하지 않더라도 통신이 회복되면 약 1초 이내에 최신 값이 됩니다.
- 소유권을 넘기거나 조작 권한을 반환할 때는 그 전에 UDP로 보낸 값을 WebSocket으로 재전송합니다. 새로운 쓰기 주체는 최신 상태를 WebSocket으로 받은 뒤 쓰기를 시작합니다.
- 상태 업데이트에는 보낸 순서의 번호가 붙습니다. UDP와 WebSocket으로 도착하는 순서가 바뀌더라도 오래된 값이 새 값을 덮어쓰지 않습니다. 예측에 사용하는 룸의 상태도 도착 순서가 아니라 이 번호로 구성합니다.
- RPC 등의 직전에 전송하는 값은 WebSocket으로 전송하므로 RPC보다 먼저 도착합니다.
- Web 브라우저(Unity의 WebGL, Godot의 Web 버전)에서는 UDP를 사용할 수 없습니다. WebSocket만으로 동작합니다.

## 오브젝트 카탈로그 {#catalog}

룸이 플레이어 오브젝트의 종류, 필드, RPC, 소유권 요청을 검사하게 하려면 Realtime 네임스페이스 설정의 `syncSetting.objectCatalog`에 검사 기준을 JSON으로 설정합니다. 키는 Prefab ID입니다.

설정하지 않으면 룸은 이것들을 검사하지 않습니다. 등록되지 않은 Prefab도 생성 시 선언한 필드를 가진 플레이어 오브젝트로 생성할 수 있고, 어떤 RPC든 누구나 호출할 수 있으며, 누구나 소유권을 취득할 수 있습니다. 설정하면 등록한 Prefab, 필드, RPC만 통과합니다.

```json
{
  "1": {
    "fields": 63,
    "mode": "player",
    "methods": [1],
    "publicMethods": [2],
    "ownership": "takeover",
    "keepOnLeave": true
  }
}
```

| 항목 | 내용 |
| --- | --- |
| `fields` | 동기화할 필드의 비트 마스크. 위치 X/Y/Z가 1/2/4, 회전 X/Y/Z가 8/16/32 |
| `mode` | 플레이어 오브젝트는 `"player"` |
| `methods` | 소유자가 호출할 수 있는 RPC의 ID |
| `publicMethods` | 참가자 누구나 다른 플레이어의 오브젝트에 대해 호출할 수 있는 RPC의 ID |
| `ownership` | `"fixed"`(기본값) 또는 `"takeover"`(누구나 소유권을 취득할 수 있다) |
| `keepOnLeave` | `true`라면 소유자 퇴장 시 삭제하지 않고 마스터 클라이언트에게 인계한다. 카탈로그에 등록한 Prefab에서만 사용할 수 있다 |

직접 작성하는 대신 에디터에서 카탈로그의 JSON을 생성할 수 있습니다([Unity](#catalog)·[Godot](#catalog)). 룰이 정의하는 Prefab(공유 오브젝트와 Simulated 오브젝트)은 생성되는 JSON에 포함되지 않습니다. 카탈로그와 룰 양쪽에서 같은 Prefab ID를 정의하면 룸의 초기화가 실패합니다.

## 오브젝트의 수명과 재접속 {#lifetime}

| 대상 | 접속이 끊겼을 때 | 재접속했을 때 |
| --- | --- | --- |
| 서버가 만든 공유 오브젝트 | 룸에 남는다 | 스냅샷으로 그 시점의 값 그대로 다시 표시된다 |
| 자신의 플레이어 오브젝트 | 삭제된다(`keepOnLeave`라면 마스터에게 인계된다) | 필요하면 다시 만든다 |
| 자신의 플레이어 프로퍼티·Buffered RPC와 이벤트 | 삭제된다 | 필요하면 다시 설정한다 |
| 룸의 프로퍼티 | 룸에 남는다 | 스냅샷으로 도착한다 |
| 가지고 있던 조작 권한 | 반환된다 | 필요하면 다시 취득한다 |
| 자신의 접속 ID | 무효가 된다 | 새로운 ID가 발행된다 |

재접속할 때마다 도착한 스냅샷으로부터 표시를 다시 만듭니다. 클라이언트에서 룰이나 조작 이력을 다시 실행할 필요는 없습니다. 재접속 전에 기다리던 요청은 철회(Abandoned)로 완료됩니다.

같은 userId로 다시 접속하면 이전 접속은 퇴장한 것으로 취급됩니다.

## 룸 종료 조건과의 관계 {#idle}

룸은 [참가자 전원으로부터 1분간 통신이 없으면 종료됩니다](#룸-종료-조건). `sync`와 `stateMachine` 룸에서는 접속을 유지하기 위한 Ping은 이 통신에 포함되지 않습니다.

클라이언트는 값이 바뀌었을 때만 전송합니다. UDP 등록 유지도 이 통신에 포함되지 않습니다. 따라서 전원이 조작하지 않고 기다리는 장면이 있는 게임에서는 그동안에도 룸이 유지되도록 정기적으로 조작을 전송하는 등의 설계가 필요합니다.

## 전송량 기준 {#size}

동기화 데이터는 필드 이름을 전송하지 않고, 변경된 필드의 번호와 정수값만 전송합니다. 클라이언트는 일정 간격(기본값 20회/초)으로 변경을 모아서 전송합니다. 같은 값을 여러 번 쓰거나 이전에 전송한 값으로 되돌아가도 전송량은 늘어나지 않습니다.

위치의 단위 간격과 동기화할 축을 필요한 범위로 좁히면 작은 흔들림으로 인한 전송을 줄일 수 있습니다.

## 잘 동작하지 않을 때 {#troubleshooting}

엔진에 공통인 확인 사항입니다. 엔진별 확인 사항은 [Unity 클라이언트](#troubleshooting)와 [Godot 클라이언트](#troubleshooting)를 참조해 주세요.

| 증상 | 확인할 사항 |
| --- | --- |
| 오브젝트가 표시되지 않는다 | 접속 후에 스냅샷을 수신하고 있는지. 도착하는 Prefab ID가 클라이언트의 카탈로그에 등록되어 있는지 |
| 값의 크기나 위치가 어긋난다 | 룰(GSL)과 동기화 스키마를 같은 룰 정의로부터 동시에 생성한 것으로 맞추었는지. 맞지 않으면 클라이언트가 "generated from other rules" 경고를 출력한다 |
| 조작이 거부된다 | 거부 알림으로 거부된 커맨드, 대상 ID, 사유(`Forbidden`: 권한 없음, `MissingObject`: 오브젝트 없음, `LimitReached`: 상한, `MethodNotAllowed`: 카탈로그에 없는 RPC 등)를 확인한다 |
| 공격 등의 조작이 반영되지 않는다 | 요청의 결과를 확인한다. [결과 코드 목록](#interaction)을 참조 |
| RPC가 도착하지 않는다 | 오브젝트 카탈로그를 설정한 경우, RPC의 ID가 카탈로그의 `methods`(다른 플레이어로부터 호출한다면 `publicMethods`)에 있는지. 메서드 이름을 바꾸면 ID도 바뀐다 |
| 생성한 오브젝트가 사라진다 | 생성 거부의 결과를 확인한다. 소유 수의 상한(64)을 초과하지 않았는지. 오브젝트 카탈로그를 설정한 경우, Prefab이 `"mode": "player"`로 등록되어 있는지 |
| UDP가 사용되지 않는다 | 룸의 포트가 UDP로 열려 있는지. Web 브라우저에서는 UDP를 사용할 수 없다 |



