← 모든 글

아무도 접속하지 않아도 살아 있는 픽셀 마을: NPC와 유령

픽셀 마을에 사람이 사는 느낌을 주는 것은 대부분 가만히 서 있는 사람들입니다. Pokémon Emerald의 16개 마을과 도시에서 숨김 플래그가 없어 언제 찾아가도 그 자리에 있는 주민 100명은, 자기 자리에 서서 한 방향을 보거나 주위를 둘러보는 사람이 59명, 배치된 곳에서 한두 셀 범위 안을 돌아다니는 사람이 39명, 제자리걸음을 하는 사람이 2명으로 나뉩니다. 스토리 플래그로 사라질 수 있는 58명(대부분 악의 조직 단원, 라이벌, 이름 있는 인물이며 그중 54명이 서 있습니다)을 더하면 158명 중 113명(71.5%), 43명(27.2%), 2명이 됩니다. 돌아다니는 사람은 16프레임 동안 한 걸음을 걷고 그다음 32, 64, 96, 128프레임 중 하나만큼 기다리므로, 움직이는 시간은 많아야 전체의 11~33퍼센트입니다.1 하루의 일은 아무도 플레이하지 않는 동안 흘러가는 시계가 아니라, 플레이어가 돌아왔을 때 경과한 날짜 수를 받아 한 번 실행되는 함수 하나로 정산됩니다.23 아무도 없을 때 원전은 다른 사람들의 흔적을 작은 고정 슬롯에 담아 마을을 채우고(Emerald의 기록 교환은 비밀기지 20개, 유행 문구 5개, 그리고 다른 플레이어의 할아버지를 내 게임에 복사합니다), 낯선 사람들이 말은 나누지 않고 존재감만 나누게 합니다.45 이 모든 것을 측정한 이유는 제 마을인 Kiradex World도 첫 번째 조용한 아침에 같은 질문에 답해야 하기 때문입니다. 그리고 서버 자체의 코드를 시뮬레이션한 시계로 돌려 보니 세 가지 점에서 답이 좋지 않았습니다. 네 명의 수집가는 방이 비면 멈춥니다. 설정값 0.7초와 달리 1초에 한 번 걸으며 한 번의 걷기 중 75퍼센트 동안 가만히 서 있습니다. 그리고 앱 자체의 그리기 코드를 모델로 만들어 보면, 한 걸음 도중에 이동이 도착할 때 다른 수집가가 뒤로 튀듯 그려집니다. 달리기에서는 64번의 이동 중 45번에서, 40타일 걷기에서는 네트워크 지터가 30ms와 80ms일 때 각각 7프레임과 16프레임에서 일어나지만, 지터가 없는 꾸준한 걷기에서는 한 번도 일어나지 않습니다.67 이 글은 측정한 원전, 현대의 참고 사례, 수치를 곁들인 실시간 존재감, 아이들이 들어올 수 있는 세계를 위한 규칙, 그리고 브리프를 차례로 다룹니다.

TL;DR

  • 마을 사람 대부분은 서 있습니다. Emerald의 마을과 도시에서 숨김 플래그가 없는 100명 중 열에 여섯은 기본 이동 유형이 정지형이고, 스토리로 사라질 수 있는 58명까지 세면 158명 전체의 열에 일곱이 됩니다(그래도 스토리 스크립트는 서 있는 사람을 걷게 할 수 있으며, 등화시티(Petalburg)의 체육관 소년은 플레이어를 체육관까지 데려갑니다). 나머지 거의 전부는 한두 셀 범위를 플레이어 자신과 같은 속도, 즉 셀당 16프레임(268ms)으로 돌아다니며 걸음 사이에 0.54~2.14초를 기다립니다. 미로마을(Littleroot)에는 6명, 등화시티에는 7명, 보라시티(Mauville)에는 10명이 있고, 그중 숨김 플래그가 없는 사람은 각각 2명, 3명, 6명입니다.18
  • 일과는 시각이 붙은 지점 몇 개와 키 하나이고, 기억은 하루 한 개의 플래그입니다. Stardew Valley는 주민의 하루를 시각이 붙은 지점의 문자열로 기록하고(Pierre의 평범한 날은 7개 지점), 어떤 날을 적용할지는 세 그룹으로 나뉜 22개의 키 형식 중에서 고르며, 가장 먼저 일치한 것이 채택됩니다. 먼저 모두에게 공통으로 확인하는 특별한 키 하나, 그다음 결혼한 주민이라면 결혼용 형식 5개(다른 것은 쓰지 않습니다), 그 밖의 모든 주민이라면 일반 형식 16개를 시도합니다. 주민과 하루에 한 번 대화하면 우정이 +20 오릅니다. 모여봐요 동물의 숲은 그날 첫 대화에 255 중 +1을 주고, 리액션을 하루에 하나 가르쳐 줍니다.29101112
  • 빈 마을은 다른 사람들의 흔적이 채웁니다. 흔적은 고정된 슬롯에 담기고, 시간이 지나면 사라집니다. Emerald의 기록 교환은 다른 플레이어의 세계를 5,188바이트 실어 옵니다. 조인 애비뉴에서는 실제 방문자가 비워 둔 가게 슬롯을 팬들이 방문자 가치의 75%로 채웁니다(Serebii의 설명). Death Stranding의 구조물은 “destroyed by Timefall after some time”(시간이 지나면 시간비에 의해 파괴됨)이라고 합니다. Journey는 한 번에 낯선 사람 한 명만 보여 주며, 이름은 크레딧 전까지 알 수 없고 주고받을 수 있는 것은 차임 소리 하나뿐입니다.413145
  • 실시간 존재감은 전송 빈도가 아니라 버퍼의 문제입니다. 팬이 만든 Habbo 서버 에뮬레이터는 모든 방을 500ms 주기로 진행합니다. Gaffer on Games는 “3X the packet send rate”(패킷 전송 간격의 3배)를 버퍼로 둡니다. 시리즈의 초당 3.75타일과 7.5타일로 Kiradex의 걷기를 모델링해 보면, 추정 서버 시각보다 350ms 늦은 시계로 다른 수집가를 그릴 때 지터 80ms까지 뒤로 가는 프레임도, 그릴 데이터가 바닥나는 프레임도 0입니다. 반면 100ms에서는 보내는 쪽이 전송하는 동안 그려지는 600프레임 중 125~456프레임에서 데이터가 바닥납니다.15167
  • 아이들을 위한 규칙에는 날짜가 있습니다. Game Center는 아이들을 “to sending and receiving preset messages”(미리 정해진 메시지의 송수신만)로 제한합니다. Apple의 문서는 독자적인 멀티플레이어 기능이 있는 게임이라면 Screen Time이 멀티플레이어를 제한할 때 “should disable it”(그것을 비활성화해야 한다)고 말합니다. 개정 COPPA 규칙은 2026년 4월 22일부터 준수가 요구되며, 삭제 기한을 담은 서면 데이터 보존 정책을 요구합니다.171819
  • 현재의 Kiradex는 자신의 계획과 네 군데에서 어긋납니다. server/app/npcs.py에는 “A town is never empty”(마을은 결코 비지 않는다)라고 적혀 있지만 광장의 수집가들은 방에 아무도 없으면 멈춥니다. 설정값 0.7초와 달리 1.0초마다 걷습니다. 계획의 근거는 8~10Hz를 말했지만 앱은 타일마다 이동 한 번을 보냅니다(걷기에서 초당 4회, 달리기에서 6.0~6.4회). 그리고 계획은 신원과 보호자 통제를 Game Center 위에 세우는데도 Game Center의 제한 플래그를 읽는 코드가 없습니다.62021222324
  • 이것이 Kiradex에 주는 것. 여덟 명의 마을 사람을 위한 브리프입니다. 일과는 앱 자체의 시간대를 키로 삼아 방마다 하나의 시계로 돌아가고, 위치는 그 시계에서 계산합니다. 최근 방문자 여덟 명의 걸음은 서버가 고른 평범한 모습으로 이름 없는 메아리가 되어 재생되고, 7일 동안 보관되며, ID 없이 전송됩니다. 하루 한 번의 인사는 결코 줄어들지 않습니다. 렌더 버퍼는 350ms. 여섯 갈래에 각각 8~12개의 완전한 문장이 들어 있는 대사집. 그리고 일곱 가지 안전 설정. 모든 항목에는 스크립트, 테스트, 캡처 중 하나로 통과와 실패를 가릴 수 있는 검사가 붙어 있습니다.7119

1. 측정한 마을 사람들: Emerald, Stardew Valley, 동물의 숲

작은 소셜 월드는 모두 첫날 밤에 같은 질문을 맞닥뜨립니다. 플레이어가 광장에 들어섰는데 아무도 없다면 무엇이 보여야 하는가, 하는 질문입니다. 원전에는 세 가지 답이 있고, 이 글은 그것을 차례로 다룹니다. 첫째는 자기 삶을 꾸려 가는 마을 사람들입니다. 그러면 누가 온라인이든 아니든 마을은 북적입니다. 둘째는 지금은 그곳에 없는 플레이어들이 남기고 간 다른 사람들의 흔적입니다. 셋째는 사람들이 있을 때 잘 구현된 실시간 존재감으로, 아이들이 들어올 수 있는 세계를 위한 규칙의 테두리 안에 있습니다. 첫 번째 답이 가장 오래되었고 가장 잘 측정되어 있습니다. 그 대가 중 한 작품의 소스가 공개되어 있기 때문입니다.

Emerald의 사람들이 하는 일

Pokémon Emerald는 pret의 디컴파일(pokeemerald 커밋 731ad5b의 얕은 클론)에서 읽었고, 이동 유형 상수와 모든 마을과 도시 맵의 오브젝트 이벤트를 읽는 스크립트 measure_npcs.py로 셌습니다.1 Emerald에는 0x00부터 0x50까지 81가지 이동 유형이 정의되어 있습니다. 화면 속 인물이 무엇을 하는지로 묶으면, 24개는 네 구간의 고정된 고리를 걷고, 17개는 서서 한 방향을 보며(고정, 번갈아, 또는 둘러보기), 16개는 제자리에서 걷거나 종종걸음을 치거나 뛰고, 8개는 플레이어를 따라 하며, 5개는 범위 안을 무작위로 돌아다니고, 4개는 왔다 갔다 하며, 4개는 숨어 있거나 변장해 있습니다. 나머지 셋은 NONE, 플레이어, 그리고 나무열매 나무인데, 나무열매 나무는 사람이 아니라 오브젝트입니다.125

마을에서 쓰이는 것은 그중 일부뿐입니다. 16개 마을과 도시 맵에는 오브젝트 이벤트가 172개 있는데, 전부가 사람은 아닙니다. 6개는 아이템 볼, 2개는 이삿짐 트럭, 1개는 배, 5개는 생물입니다. 스크립트는 이것들을 graphics_id로 분류해 158명의 사람만 따로 셉니다.18 81가지 이동 유형 중 사람에게 나타나는 것은 11가지뿐입니다. 158명 중 113명(71.5%)은 서서 한 방향을 보고, 43명(27.2%)은 범위 안을 돌아다니며, 2명(1.3%)은 제자리걸음을 합니다. 단일 유형으로 가장 흔한 것은 FACE_DOWN 32명, FACE_RIGHT 23명, LOOK_AROUND 20명, FACE_UP 19명, WANDER_AROUND 17명입니다.18

이 158명이 모두 주민인 것은 아닙니다. 58명은 숨김 플래그, 즉 그 사람을 맵에서 지우는 스토리 플래그를 가지고 있습니다. 58명 중 41명은 악의 조직, 라이벌, 이름 있는 인물의 스프라이트를 쓰므로, 58명은 대부분 스토리 장면에 나오는 배우입니다. 그중 54명은 서 있습니다. 숨김 플래그가 없는 100명, 즉 찾아갈 때마다 맵에 있는 사람들은 다르게 나뉩니다. 59명이 서서 한 방향을 보고, 39명이 범위 안을 돌아다니며, 2명이 제자리걸음을 합니다. 그러니까 플레이어가 대개 보게 되는 주민은 열에 여섯이 서 있고 열에 넷 가까이가 돌아다닙니다. 전체의 열에 일곱이라는 수치는 거의 다 서 있는 배우들을 더해서 나온 것입니다. 이것들은 기본 이동 유형, 즉 스크립트가 움직이지 않을 때 그 인물이 하는 일입니다. 스토리 스크립트는 서 있는 사람도 걷게 할 수 있습니다. 등화시티의 체육관 소년은 기본적으로 주위를 둘러보고 숨김 플래그도 없지만, 플레이어를 체육관까지 데리고 걸어갑니다. 이 집계가 보여 주는 것은 쉬고 있는 마을이지, 마을의 모든 장면이 아닙니다.18

Emerald 마을 오브젝트 이벤트 가운데 사람을 이동 유형별로 나타낸 누적 막대그래프. 16개 마을과 도시 전체, 158명: 113명이 서서 한 방향을 봄(71.5퍼센트), 43명이 범위 안을 돌아다님(27.2퍼센트), 2명이 제자리걸음. 숨김 플래그가 없는 100명: 59명이 서 있음(59퍼센트), 39명이 돌아다님(39퍼센트), 2명이 제자리걸음. 미로마을, 6명: 3명이 서 있고 3명이 돌아다님. 등화시티, 7명: 5명이 서 있고 2명이 돌아다님. 보라시티, 10명: 8명이 서 있고 2명이 돌아다님.

Emerald의 맵에 늘 있는 마을 사람 열에 여섯은 기본 이동 유형이 정지형이고, 스토리에서 사라질 수 있는 배우까지 세면 열에 일곱입니다. 돌아다니는 사람은 한두 셀 범위를 벗어나지 않습니다. 누구든 걷게 할 수 있는 스크립트 장면은 세지 않았습니다.26

크기가 다른 세 마을이 같은 모양을 보여 줍니다. 표는 사람만 셉니다. 제외한 오브젝트 이벤트는 미로마을의 트럭 두 대와 아이템 볼 3개(등화시티에 2개, 보라시티에 1개)입니다. 마지막 열은 숨김 플래그가 없는 사람을 하는 일별로 센 것입니다.1

마을 사람 서서 한 방향을 봄 범위 안을 돌아다님 돌아다니는 범위 (x, y) 숨김 플래그 있음 숨김 플래그 없음 (서 있음, 돌아다님)
미로마을 6 3 (위를 봄) 3 (WANDER_AROUND) (1, 2), (2, 1), (2, 1) 4 2 (0, 2)
등화시티 7 5 (둘러보기 2, 위를 봄 2, 아래를 봄 1) 2 (사방 1, 위아래 1) (1, 1), (0, 1) 4 3 (2, 1)
보라시티 10 8 (둘러보기 2, 위를 봄 2, 왼쪽을 봄 2, 오른쪽을 봄 1, 아래를 봄 1) 2 (좌우) (1, 1), (1, 0) 4 6 (4, 2)

돌아다니는 사람의 범위는 한 셀씩 강제됩니다. 한 걸음을 내딛기 전에 IsCoordOutsideObjectEventMovementRange가 배치된 곳에서의 범위를 벗어나는 목표를 거부하므로, 돌아다니는 사람은 멀리 가는 대신 자기 집 셀 주위를 맴돕니다.27 숨김 플래그는 주민 구성의 나머지 절반입니다. 미로마을 6명 중 4명, 등화시티 7명 중 4명, 보라시티 10명 중 4명이 스토리가 진행되면 자신을 지우는 플래그를 갖고 있습니다. 그러니 마을의 주민 구성은 플레이어 진행도의 함수입니다. 사람들이 떠나고 나타나는 것은 무언가가 일어났기 때문입니다.1

얼마나 자주 움직이는가

타이밍도 같은 파일에 있습니다. 돌아다니거나 둘러보는 마을 사람은 다음 걸음이나 방향 전환 전에 sMovementDelaysMedium에서 무작위로 고른 32, 64, 96, 128프레임 중 하나만큼 기다립니다. Game Boy Advance의 59.7275Hz로 0.54, 1.07, 1.61, 2.14초이며 평균은 1.34초입니다. 정반대의 두 방향을 번갈아 보는 두 유형 FACE_DOWN_AND_UP과 FACE_LEFT_AND_RIGHT도 같습니다. 이웃한 두 방향을 번갈아 보거나 세 방향 사이를 보는 유형은 sMovementDelaysShort, 즉 32, 48, 64, 80프레임 중에서 고르며 평균은 0.94초이고, 회전하는 두 유형은 48프레임 고정으로 기다립니다. 세 번째 테이블 sMovementDelaysLong은 소스에 // Unused로 표시되어 있고, 어떤 이동 유형도 이것을 읽지 않습니다.127

걸음 자체는 플레이어의 것과 같습니다. PlayerWalkNormal은 GetWalkNormalMovementAction을 호출하는데, 이는 돌아다니는 사람의 걸음이 쓰는 것과 같은 동작입니다. MOVE_SPEED_NORMAL은 sStep1Funcs에 대응하며 16프레임 동안 프레임당 1픽셀씩 움직여, 16픽셀 한 셀을 268ms에, 초당 3.73셀로 갑니다. 달리기는 MOVE_SPEED_FAST_1, sStep2Funcs로 셀당 8프레임, 134ms, 초당 7.47셀입니다.127 두 줄을 합치면 돌아다니는 사람은 걸음 하나와 대기를 합친 0.81~2.41초 중 268ms 동안 움직입니다. 내딛은 걸음 하나 기준으로 시간의 11~33퍼센트입니다. 이것은 상한입니다. 돌아다니는 사람이 고른 방향이 벽이나 범위 끝에 막혀 있으면 MovementType_WanderAround_Step4가 움직이지 않고 다시 방향을 돌려 기다리게 하므로, 실제로 움직이는 시간의 비율은 더 낮습니다. 나머지 시간 동안 그 사람은 서 있거나 방향을 바꿉니다.127

6초 동안의 타임라인 그림. Emerald의 돌아다니는 사람을 대기 길이별로 네 줄로 나타냄: 대기가 32프레임이면 시간의 33퍼센트 동안 움직이고, 64프레임이면 20퍼센트, 96프레임이면 14퍼센트, 128프레임이면 11퍼센트. 다섯 번째 줄은 걷기 구간에 있는 Kiradex 수집가로, 1.0초마다 250ms 동안 움직여 25퍼센트.

Emerald의 돌아다니는 사람은 긴 대기 사이에 한 걸음씩 움직입니다. 6절에서 측정하는 Kiradex의 수집가들은 걷고 있어야 할 동안 한 타일 길이의 짧은 움직임을 되풀이합니다.26

제가 읽기로는 효과를 만드는 세부가 세 가지입니다. 마을 안에는 플레이어보다 빨리 걷거나 느리게 걷는 것이 없으므로, 이질적으로 보이는 것이 없습니다. 대기가 걸음보다 길어서 마을은 정지된 장면들 사이사이를 움직임이 끊어 주는 모습이 되고, 그래서 6~10명이 있어도 붐벼 보이지 않습니다. 그리고 주민 구성이 숨김 플래그를 통해 스토리에 쓰여 있으므로, 마을이 바뀌는 것은 타이머가 울렸기 때문이 아니라 플레이어가 무언가를 했기 때문입니다.

Emerald의 하루는 도착할 때 정산됩니다

Emerald는 날짜를 관리하지만, 아무도 플레이하지 않는 동안 흐르는 시계로 관리하지는 않습니다. DAILY_FLAGS_START 뒤에 매일 플래그 슬롯 64개를 마련해 두고 그중 12개를 씁니다. 나무열매 선물 아홉 개(콘테스트 로비, 나무열매 명인과 그 아내, 길이나 마을에서 주는 다섯 명, 꽃가게), 비밀기지의 매일 플래그, 복권, 그리고 FLAG_DAILY_APPRENTICE_LEAVES입니다.228 플레이어가 돌아오면 UpdatePerDay가 지난 날짜 수를 받아 한 번 실행되며, 플레이 중에도 DoTimeBasedEvents에서 실행됩니다. 이 함수는 차례로 ClearDailyFlags, UpdateDewfordTrendPerDay, UpdateTVShowsPerDay, UpdateWeatherPerDay, UpdatePartyPokerusTime, UpdateMirageRnd, UpdateBirchState, UpdateFrontierManiac, UpdateFrontierGambler, SetShoalItemFlag, SetRandomLotteryNumber를 호출합니다.23 유행, 텔레비전, 날씨는 모두 돌아온 순간에 계산되는 경과 일수에 따라 움직입니다.3 카트리지가 꺼져 있는 동안 아무 일도 일어나지 않았지만, 마을은 일어난 것처럼 행동합니다.

Stardew Valley: 일정은 문자열입니다

삶을 가진 마을 사람에 관해 현대의 참고 사례는 Stardew Valley입니다. 위키의 Villagers 페이지에는 독신 남성 6명, 독신 여성 6명, 결혼할 수 없는 주민 22명, 선물을 줄 수 없는 주민 12명이 올라 있어, 선물을 줄 수 있는 주민은 34명입니다. 페이지에는 “Each villager has a daily routine, so they can be located in different sections of town depending on the in-game time of the day and weather.”(주민마다 일과가 있어서, 게임 속 시각과 날씨에 따라 마을의 서로 다른 곳에 있을 수 있다)라고 적혀 있습니다.229

일과는 데이터입니다. 주민마다 Content/Characters/schedules/ 아래에 파일이 있고, 각 항목은 슬래시로 구분된 지점들의 문자열 하나입니다. 각 지점은 <time> [location] <tileX> <tileY> [facing] [animation] [dialogue] 형태이며, 시각은 콜론 없는 24시간 표기, 방향은 0이 위, 1이 오른쪽, 2가 아래, 3이 왼쪽입니다.9 페이지에 실린 Abigail의 수요일은 1000 ArchaeologyHouse 11 9 0/1800 Town 47 87 0/2200 SeedShop 1 9 3 abigail_sleep으로, 세 지점과 각 지점 사이의 이동, 그리고 마지막 지점의 애니메이션으로 이루어집니다.9 잡화점을 운영하는 Pierre는 평범한 날에 시각이 붙은 지점이 7개 있고(오전 6시 계산대, 오전 7시 통로, 오전 8시 30분 다시 계산대, 그다음 오후 5시, 7시, 9시, 11시에 통로, 부엌, 책장, 침대를 거칩니다), 그의 페이지에는 일정의 변형이 6가지 올라 있습니다. 초록 비, 봄 15일에 수리된 버스, 사막 축제, 비, 금요일, 그리고 평범한 날입니다.230

영리함은 키에 있습니다. 어느 날 어떤 일정이 돌아갈지는 세 그룹으로 나뉜 22개의 키 형식이 정하며, 각각 정해진 순서로 시도되고 가장 먼저 일치한 것이 채택됩니다. 특별한 키 GreenRain은 하나뿐이며 “checked first, regardless of marriage status.”(결혼 여부와 관계없이 가장 먼저 확인됨)입니다. 결혼한 주민은 그다음 결혼용 형식 5개만 시도합니다. marriage_<festivalID>부터 날짜, marriageJob, 요일까지입니다. “Married NPCs don’t use any other schedule keys. If the marriage keys don’t match, they won’t have a schedule for that day.”(결혼한 NPC는 다른 일정 키를 쓰지 않는다. 결혼용 키가 일치하지 않으면 그날은 일정이 없다.) 그 밖의 모든 주민은 일반 형식 16개를 시도합니다. <festivalID>, <season>_<dayofmonth>, <dayofmonth>_<hearts>, <dayofmonth>, bus, rain2(“50% chance of applying on rainy days”, 비 오는 날 50% 확률로 적용), rain에서 시작해 요일과 하트 키, 요일을 거쳐 <season>, spring, 마지막으로 default에 이릅니다.29 그러니 제가 읽기로는, 주민 한 명에게는 가능한 하루가 여럿 있고, 같은 몇 개의 지점이 다시 조합되어 삶이 되며, 하트 키 때문에 플레이어를 좋아하는 주민은 다른 하루를 보냅니다. 페이지의 제약 사항 메모는 하루가 어떻게 계산되는지 알려 줍니다. 기존 세이브에 NPC가 추가되면 “they generally don’t follow their schedule correctly until you’ve slept once in-game (which triggers their first day update).”(게임 안에서 한 번 잠들기 전까지는 대개 일정을 제대로 따르지 않는다. 잠이 첫 하루 업데이트를 일으키기 때문이다.)9 Emerald처럼 Stardew의 하루도 연속적으로 흘러가는 것이 아니라 하루의 경계에서 계산됩니다.

우정은 매일의 기억이며 대가가 따릅니다. 하트 하나는 250포인트입니다. 위키의 우정을 올리는 방법 목록에서 첫 항목은 “talking to them once per day (+20)”(하루에 한 번 대화하기, +20)입니다. 주민은 선물을 하루에 하나, 일주일에 두 개까지 받으며, 생일 선물은 8배가 됩니다. 그리고 대화하지 않으면 매일 포인트가 깎입니다. 대부분의 주민은 2, 꽃다발을 준 뒤에는 10으로, 게이지가 가득 차거나 상한에 이를 때까지 계속되고, 배우자는 20이며 이 감소는 결코 멈추지 않습니다.10 대본이 있는 장면인 하트 이벤트는 하트 기준점에서 열리며 “most events can be viewed anytime (with some time restrictions) or out of order.”(대부분의 이벤트는 시간 제약이 있긴 하지만 언제든, 또는 순서와 상관없이 볼 수 있다)라고 합니다.10 주민은 플레이어가 없는 동안에도 플레이어에게 무언가를 합니다. 하트 3개에서 Pierre는 “will send you a recipe in the mail”(우편으로 레시피를 보내 준다)하고, 하트가 0보다 크기만 하면 어느 단계에서든 250g 이상을 보내올 수 있습니다.30

동물의 숲: 플레이어가 들렀다는 것을 아는 주민

모여봐요 동물의 숲은 오늘 말을 걸어 주었다는 것을 아는 주민을 단 1포인트로 줄여 버립니다. 우정은 0부터 255까지이며 25에서 시작합니다. Nookipedia의 우정을 올리는 것 목록의 첫 항목은 “speaking to the villager for the first time each day”(그날 처음으로 주민에게 말 걸기)로 “+1 point”입니다. 주민은 선물을 하루에 하나 받습니다. 그리고 150포인트에서는 주민이 선물을 받고 사진을 줄 확률이 6%이며, 포인트마다 0.04%씩 올라 255에서는 10.2%가 됩니다.11 제가 읽기로는 플레이어가 그 움직임을 볼 일이 없으므로 이것은 게이지가 아니라 플래그입니다.

주민의 눈에 보이는 삶은 작고 그들만의 것입니다. 각자 여섯 가지 취미(교육, 패션, 피트니스, 음악, 자연, 놀이) 중 하나를 가지며, 모여봐요 동물의 숲에서는 “a villager always has one hobby that doesn’t change”(주민은 늘 바뀌지 않는 취미 하나를 가진다)라고 합니다. 주민들은 벌레와 물고기를 쫓지만 “still will not catch either of them”(그래도 어느 쪽도 잡지 않는다). 운동하고, 책을 읽고, 근처 음악 플레이어에 맞춰 노래하며, 2.0 업데이트 이후로는 “invite the player to their house, ask the player for an invitation”(플레이어를 자기 집에 초대하고, 플레이어에게 초대를 부탁한다)하거나 “a random visit”(불시의 방문)으로 찾아옵니다.3132 말하는 내용은 여덟 가지 성격이 빚어냅니다. 남자 주민에게 네 가지(먹보, 운동광, 무뚝뚝, 느끼함), 여자 주민에게 네 가지(친절함, 아이돌, 성숙함, 단순활발)이며, 그중 “Smug and big sister were introduced in New Leaf, with the other six being present since Doubutsu no Mori.”(느끼함과 단순활발은 튀어나와요 동물의 숲에서 도입되었고, 나머지 여섯은 동물의 숲 첫 작품부터 있었다)라고 합니다.33

시리즈 내내 마을은 작게 유지되었습니다.32

게임 시작 시 주민 수 최대 주민 수
Animal Crossing 6 15
놀러오세요 동물의 숲 3 8
타운으로 놀러가요 동물의 숲 6 10
튀어나와요 동물의 숲 5 10
모여봐요 동물의 숲 2 (가능한 83명 중 운동광 한 명과 단순활발 한 명) 10

놀러오세요 동물의 숲은 속도도 바꾸었습니다. 그 게임에서는 “the villagers walk at a much slower pace than the player”(주민들은 플레이어보다 훨씬 느린 속도로 걷는다)였고, 타운으로 놀러가요 동물의 숲이 이를 이어받았습니다.32 이것은 Emerald의 규칙과 정반대이지만, 제가 읽기로는 둘 다 같은 이유로 통합니다. 느긋하게 걷는 주민은 주민답게 보이고, 플레이어와 같은 속도로 한 걸음 내딛고 기다리는 마을 사람도 주민답게 보입니다. 효과를 깨뜨리는 것은 어느 쪽 속도도 아닌 방식으로 움직이는 인물이며, 그것이 6절에서 다루는 Kiradex의 문제입니다.

시리즈의 정해진 표현 수단은 리액션이며, 리액션 자체가 마을 사람들이 날마다 조금씩 나눠 주는 선물입니다. 모여봐요 동물의 숲의 리액션은 1.0.0에서 44개였고 2.0까지 44개가 더해져 모두 88개가 되었으며, “The player can learn one Reaction per day”(플레이어는 리액션을 하루에 하나 배울 수 있다)라고 합니다. 일부는 특정 성격의 주민에게서만, 또는 우정이 높을 때만 배울 수 있습니다.212

2. 다른 사람들의 흔적: 아무도 온라인이 아닐 때 마을을 채우는 것

마을 사람들이 있으면 마을은 북적이지만, 날마다 같은 얼굴들입니다. 두 번째 답은 빈 세계를 걱정했을 때 살펴본 모든 게임이 손을 뻗은 것입니다. 지금 온라인일 필요가 없는 플레이어들이 남긴, 진짜 사람들의 흔적으로 세계를 채우는 것입니다. Emerald는 이를 통신 케이블로 해냈습니다.

기록 교환: 고정된 슬롯에 담긴 다른 플레이어의 세계

두 Emerald 플레이어가 기록 교환(record mixing)을 하면, 각 게임은 상대에게 고정된 구조체 PlayerRecordEmerald를 보냅니다. 11가지 기록으로 이루어진 0x1444바이트, 모두 5,188바이트입니다.434 다른 사람들이 여기 있었다는 느낌에 중요한 숫자는 슬롯입니다. 비밀기지 20개, 텔레비전 슬롯 25개(일반 5개와 추가 20개), 뉴스 16개, 유행 문구 5개, 그리고 게임이 보관하는 제자 4명 중 2명입니다.4 제가 읽기로는 그것이 슬롯의 요점입니다. 친구의 기지, 그 방송, 그 문구가 내 게임으로 옮겨져, 다른 누군가가 플레이했기 때문에 내 마을이 바뀐 것입니다.

가장 깔끔한 예는 한 사람입니다. 보라시티의 포켓몬센터에는 할아버지(Old Man)가 한 명 있는데, 그는 다섯 명 중 하나입니다(음유시인(Bard), 유행을 좇는 사람(Hipster), 교환꾼(Trader), 이야기꾼(Storyteller), 들뜬 사람(Giddy)). 새 게임에서 누가 될지는 (trainerId % 10) / 2로 정해지며, 소스 자체의 주석은 “Determine man based on the last digit of the player’s trainer ID”(플레이어 트레이너 ID의 끝자리로 인물을 정한다)입니다. 그리고 기록을 교환하면 ReceiveOldManData가 상대의 할아버지를 내 할아버지 위에 덮어 복사합니다.435 내 마을의 주민은 말 그대로 다른 플레이어의 마을 사람입니다.

유행을 좇는 사람은 어휘를 만남 단위로 배급합니다. 그의 스크립트는 FLAG_UNLOCKED_TRENDY_SAYINGS를 설정하고, taughtWord가 false이면 단어 하나를 가르친 뒤 그것을 true로 바꿉니다. 이를 다시 false로 되돌리는 유일한 호출, 즉 ResetMauvilleOldManFlag를 거치는 ResetHipsterFlag는 정확히 한 곳, record_mixing.c의 ReceiveOldManData 끝에서만 호출됩니다.35 그러니 유행어(Trendy Saying)는 기록 교환으로 유행을 좇는 사람이 올 때마다 한 번, 그리고 처음부터 그를 가진 플레이어라면 한 번 더 풀립니다. 선택 규칙에 따르면 그런 플레이어는 트레이너 ID 끝자리가 2나 3인 사람입니다. 이 시리즈의 첫 번째 글도 같은 규칙을 같은 방식으로 설명합니다.3635 어휘는 기다려서가 아니라 사람을 만나서 늘어납니다.

유행을 산수로 보기

유행 문구는 무로마을(Dewford)이 지금의 유행어로 여기는 간이 회화(Easy Chat) 단어 두 개로, Emerald가 점수를 매기는 유일한 흔적입니다. 소스 주석에 따르면 “boring”(지루한) 문구는 “lose trendiness over time until it reaches 0, at which point it will stop being boring and gain trendiness until it reaches maxTrendiness (then it becomes boring again and the cycle repeats).”(시간이 지나면서 유행도를 잃어 0에 이르고, 그 시점에서 지루함을 멈추고 maxTrendiness에 이를 때까지 유행도를 얻는다. 그러면 다시 지루해지고 주기가 반복된다)라고 합니다.37 문구의 정점은 최대 세 번 중첩된 Random() % 98 추첨으로 30에서 127 사이에서 정해집니다. 각 추첨이 98개의 값에 대해 독립적이고 균등하다고 보면, 정점의 평균은 62.9이고 정점의 81.3%가 80 이하입니다. 시작 점수는 30과 정점 사이에서 균등하며 평균은 46.5입니다. 그리고 점수는 경과한 하루마다 5씩 움직이고, 다른 모든 것과 마찬가지로 UpdatePerDay에서 정산됩니다.383 이 수치들은 코드의 근사이지, 코드의 정확한 출력이 아닙니다. Random()은 16비트를 반환하므로 0에서 71까지의 나머지는 65,536번 중 669번, 72에서 97까지는 668번 나오고, 연속된 추첨은 독립된 주사위가 아니라 생성기 하나에서 나옵니다. 나머지에 그렇게 가중치를 주어도(추첨은 여전히 독립이라고 가정) 소수점 첫째 자리에서는 어느 수치도 변하지 않습니다.3837 연속적인 속도로 보면, 정점 30인 문구는 0에서 정점까지 올랐다가 돌아오는 데 12일, 정점 64는 25.6일, 정점 127은 50.8일이 걸립니다. 게임은 점수를 하루 단위로 움직이고, 정점이나 0을 넘어설 점수는 그 나머지만큼 되튀므로, 날마다의 점수는 이 소수 주기로 반복되지 않습니다. 0에서 오르기 시작하는 점수부터 하루씩 옮겨 계산하면, 정확한 수열이 처음 반복되는 것은 12일, 128일, 254일 뒤입니다.3837 유행은 최대 5개까지 저장되며, 교환할 때 “their own trends are replaced with their mixing partner’s, unless the phrase is the same, in which case the version with a higher trendiness value is used.”(자신의 유행은 교환 상대의 유행으로 바뀐다. 단 문구가 같으면 유행도가 더 높은 쪽이 쓰인다)입니다.3837

Emerald의 유행 점수를 60일 동안 하루에 한 점씩 나타낸 꺾은선 그래프. 세 가지 정점에 대해 각각 0에서 시작해 하루 5씩 움직이며 정점과 0에서 되튐. 정점 30은 12일마다 오르내리며 정확히 반복됨. 정점 64는 약 25.6일마다 오르내림(연속적인 근사)이며, 날마다의 점수가 정확히 반복되는 것은 128일 뒤. 정점 127은 약 50.8일마다 오르내리며, 날마다의 점수는 254일 뒤에 반복됨.

유행은 하루에 한 번 표본을 뜨는 느린 지그재그입니다. 정점의 81.3%가 80 이하이고, 오르내림이 약 32일 이내입니다.2638

제가 읽기로는 마을이 낡지 않게 지켜야 하는 흔적으로서 이것이 올바른 모양입니다. 오르고, 내리고, 수명은 태어날 때 정해지며, 다른 플레이어와 만나면 더 새로운 것으로 바뀔 수 있습니다.

간이 회화와 유니언 룸

Emerald에는 플레이어끼리 무언가를 전하는 방법이 두 가지 있었고, 이 둘은 이 글의 안전 논의의 양 끝에 해당합니다. 로컬 무선 로비인 유니언 룸(Union Room)은 그룹 리더 8명을 보여 주고, 무선 그룹마다 5명(RFU_CHILD_MAX인 4에 1을 더함)을 받으며, 스프라이트 40개를 그리고, 활동 코드 30개를 정의합니다. 그 채팅은 키보드 입력입니다. 키보드 페이지 4개(UPPER, LOWER, EMOJI, REGISTER), 메시지당 15자(MAX_MESSAGE_LENGTH), 세이브에 보관되는 등록 문구 10개(UNION_ROOM_KB_ROW_COUNT)입니다.439 제가 읽기로는 그곳에서 키보드 입력 채팅이 용인될 수 있었던 것은 방 안의 모두가 내 무선 범위 안에 있었기 때문입니다.

플레이어 사이에 오간 다른 모든 말은 간이 회화를 거쳤습니다. 간이 회화에는 22개 그룹, 모두 1,815개 항목이 있습니다. 처음부터 열려 있는 것은 트레이너(27개, 그중 6개는 비활성), 상태(109), 배틀(63), 인사(42), 사람(75), 소리(63), 말(60), 어미(69), 기분(69), 상황(69), 행동(78), 생활(45), 취미(54), 시간(45), 기타(42), 형용사(36)와, 202개 항목의 이름 목록입니다. 이름은 각각 플레이어가 그 종을 보았을 때에만 열립니다. 잠겨 있는 것은 게임을 클리어할 때까지의 이벤트(29)와 두 개의 기술 목록(154와 200), 유행을 좇는 사람의 플래그가 설 때까지의 유행어(33), 그리고 251개 항목의 전국 이름 목록입니다.3840 어떤 플레이어도 고를 수 없는 트레이너의 비활성 항목 6개를 빼면, 이름을 세지 않고 첫 한 시간부터 열려 있는 단어는 940개입니다. 어떤 그룹이 열리는지는 easy_chat.c의 switch 하나가 정하고, 각 항목은 두 번째 판정이 정합니다. 이름이면 그 종을 보았는지, 그 밖의 단어면 enabled 필드입니다.3840 문구는 고정된 격자입니다. 프로필은 2×2로 4칸, 배틀 시작 말은 2×3으로 6칸, 메일은 2×5로 10칸에 최대 9단어, 유행 문구와 좋은 말은 2×1, 설문은 2×2로 4단어입니다.3840

940개 단어로 조합할 수 있는 어휘는 표현력이 풍부하지만, 제가 읽기로는 마음먹은 아이라면 무언가를 철자로 만들어 낼 수 있는 어휘이기도 합니다. 7절의 Kiradex 브리프가 거부하는 것이 바로 이 맞교환입니다. 단어가 아니라 언제나 완전한 문장입니다.

다른 게임 속 낯선 사람들의 흔적

빈 세계를 걱정한 다른 게임들도 모두 다른 플레이어에게 손을 뻗었고, 대부분은 플레이어가 남기고 가는 흔적으로 향했습니다. 출처의 무게는 저마다 다르므로, 각 단락은 자기 출처를 밝힙니다.

Dark Souls(FromSoftware, 2011년). Wikipedia는 자체 출처를 인용해 이렇게 씁니다. “The player can see ghostly images of other players, activate bloodstains that show how other players died, and leave messages using preset phrases.”(플레이어는 다른 플레이어의 유령 같은 모습을 보고, 다른 플레이어가 어떻게 죽었는지 보여 주는 핏자국을 활성화하고, 정해진 문구로 메시지를 남길 수 있다.)41 The Daily Telegraph는 이 게임에 “Best Integration of Online Features”(온라인 기능 최우수 통합) 상을 주었습니다.41 Bandai Namco Europe의 리마스터 페이지는 주요 특징으로 “The Way of the Multiplayer (up to 6 players with dedicated servers)”(멀티플레이어의 길, 전용 서버로 최대 6인)를 꼽을 뿐, 유령과 메시지의 층에 대해서는 더 말하지 않습니다. FromSoftware 자체의 설명에는 닿지 못했습니다.42 이 페이지는 유령 같은 모습이 떠난 플레이어를 재생한 것인지, 그 순간 접속해 있는 플레이어를 보여 주는 것인지 밝히지 않습니다. Wikipedia는 그것들을 핏자국, 메시지와 함께 게임이 “the single-player world”(싱글플레이어 세계)에 “integrates”(통합하는) “online features”(온라인 기능) 가운데 두고, “Direct multiplayer”(직접 멀티플레이어), 즉 소환과 침입은 다음 문장에 둡니다.41

Journey(thatgamecompany, 2012년). 이것도 Wikipedia입니다. “In each level, the player may come across one other player temporarily connected to their game”(각 레벨에서 플레이어는 자기 게임에 일시적으로 연결된 다른 플레이어 한 명을 만날 수 있다). 두 사람은 “cannot communicate via speech or text and cannot see each other’s names until after the game’s credits”(말이나 글로 소통할 수 없고 게임의 크레딧이 끝날 때까지 서로의 이름을 볼 수 없다). “The only form of communication between the two is a musical chime.”(두 사람 사이의 유일한 소통 방식은 음악적인 차임 소리다.) 개발자들은 “felt having text or voice communication or showing usernames would allow players’ biases and preconceptions to come between them and the other player.”(글이나 음성 소통, 또는 사용자 이름 표시가 있으면 플레이어의 편견과 선입견이 상대와의 사이에 끼어들 것이라고 느꼈다.)5 이것은 안전 논의를 디자인의 형태로 보여 준 것이며, 어른을 위해 만든 것입니다.

Death Stranding(Kojima Productions, 2019년). Sony의 페이지는 이렇습니다. “Donate valuable resources to rebuild structures in your world and others’, and offer likes in support of player structures that appear in yours.”(귀한 자원을 기부해 내 세계와 다른 사람들의 세계에 구조물을 다시 짓고, 내 세계에 나타난 플레이어 구조물에 좋아요를 보내 응원하세요.)43 Wikipedia는 플레이어가 “can leave supplies, structures, and messages that can be viewed and used by other players, although structures will eventually be destroyed by Timefall after some time”(다른 플레이어가 보고 쓸 수 있는 보급품, 구조물, 메시지를 남길 수 있지만, 구조물은 시간이 지나면 결국 시간비에 의해 파괴된다)이라는 것과 “The player does not directly encounter other players in the world.”(플레이어는 세계에서 다른 플레이어를 직접 만나지 않는다)라는 것을 덧붙입니다.14 흔적이 썩어 없어지므로 세계가 쌓인 것으로 메워지지 않습니다.

Splatoon(Nintendo, 2015년). Wikipedia는 출처를 인용해 플레이어의 Miiverse 게시물이 “appear in-game as graffiti on various buildings”(게임 속 여러 건물에 낙서로 나타난다)라고 합니다. 낙서는 게임의 Miiverse 커뮤니티에 올라온 게시물에서 왔으므로, 제가 읽기로는 이 흔적은 게임 자체의 온라인 플레이가 아니라 Nintendo의 Miiverse 서비스에 기대고 있었습니다.44

이후의 Pokémon 기능 세 가지는 오래된 팬 사이트 Serebii라는 출처 하나에 기댑니다. pokemon.com의 포켓몬스터 블랙2·화이트2와 포켓몬스터 썬·문 페이지는 조인 애비뉴도 페스서클도 언급하지 않으며, GO의 체육관에 관한 퍼블리셔 페이지는 저장되지 않았습니다. 그러니 다음 세 단락은 각각 퍼블리셔의 설명이 아니라 Serebii의 설명으로 읽어 주십시오.45

조인 애비뉴(Join Avenue, 포켓몬스터 블랙2·화이트2, 2012년). Serebii에 따르면 애비뉴는 “starts off as an empty pathway”(빈 길에서 시작한다)이고, 사람들이 와서 가게를 여는데, 이는 “is done automatically whenever you connect with someone”(누군가와 연결될 때마다 자동으로 이루어진다)으로, 엇갈림 통신, 적외선, 유니언 룸, Wi-Fi, GTS 등을 통해 일어납니다. 가게는 여덟 곳이며, “every 10th person shown to the shop, the shop will rank up”(가게에 안내한 사람이 10명이 될 때마다 가게 등급이 오른다) 하여 등급 10까지 오르고, “discounts of 1% for every rank up”(등급이 오를 때마다 1% 할인)으로 “a 40% discount”(40% 할인)에 이릅니다. 일반 방문자의 가치는 “from 100 to 200 points”(100에서 200포인트)입니다. 그리고 사천왕을 물리친 뒤에는 “various fans will come and visit”(여러 팬이 찾아온다)하며, “If you help a fan rather than another player, they will give 75% of the normal popularity points.”(다른 플레이어 대신 팬을 도우면 평소 인기 포인트의 75%를 준다)입니다.13 제가 읽기로는 원전에서 대체 규칙을 가장 깔끔하게 표현한 사례입니다. 진짜 사람들이 비워 둔 슬롯을 대역이 채우고, 그 가치는 조금 덜합니다.

페스서클(Festival Plaza, 포켓몬스터 썬·문, 2016년). Serebii에 따르면 서클은 “will propogate itself with players you will find online and locally”(온라인과 로컬에서 찾을 수 있는 플레이어들로 스스로를 채운다, 원문 그대로)이고, “This is not limited to people on your friend list”(이는 친구 목록의 사람들에 한정되지 않는다)입니다. 무언가를 원하는 손님은 그렇게 말하고, 도와주면 페스코인을 냅니다. 도와준 손님은 VIP로 만들 수 있으며, 그러면 “more likely to appear in your plaza in the future”(앞으로 내 서클에 나타날 가능성이 높아진다)가 됩니다. 서클 등급은 레벨 1에서 6코인이 들고, 레벨 101부터는 300으로 오릅니다.46 아바타를 통한 존재감입니다. 내가 상호작용하는 것은 다른 플레이어의 아바타이며, Serebii는 그 뒤의 사람들을 “all players that are also playing online”(마찬가지로 온라인에서 플레이하고 있는 모든 플레이어)이라고 하고, 그들이 “when they see you”(나를 보면) “challenge you to battles or request trades”(배틀을 걸거나 교환을 요청할) 수 있다고 설명합니다. 그 설명에 따르면 서클의 인물들은 자신도 온라인인 플레이어를 나타냅니다. Journey와 함께, 이 목록에서 출처가 흔적이 아니라 실시간 존재감으로 묘사하는 두 기능 중 하나입니다.46

Pokémon GO 체육관(2017년 개편). Serebii에 따르면 체육관에는 수비 포켓몬을 최대 여섯 마리 둘 수 있습니다. 배틀에서 질 때마다 수비 포켓몬의 의욕이 떨어지고, “Once their Motivation has dropped to 0, then they are removed from the Gym”(의욕이 0까지 떨어지면 체육관에서 내보내진다). 나무열매로 의욕을 되살릴 수 있고, 수비 포켓몬은 10분마다 1코인을 벌지만 “capped at 50 Coins earned a day.”(하루에 벌 수 있는 코인은 50개가 상한)입니다.47 그 장소는 다른 플레이어가 없는 동안에도 그 동료를 계속 보여 주며, 커뮤니티가 먹이를 주지 않으면 시들어 갑니다.

여덟 가지 모두에 대한 제 읽기로는 공통된 패턴이 있습니다. 소수의 고정된 슬롯(가게 8곳, 수비 6마리, 동료 1마리, 기지 20개), 썩거나 순환하는 흔적(시간비, 의욕, 유행의 주기), 사람보다 가치가 낮은 대역, 그리고 페스서클의 배틀과 교환 요청을 빼면 정해진 문구나 차임 소리 말고는 낯선 사람 사이에 실시간 채널이 없다는 점입니다.

3. 실시간 존재감: 빈도, 버퍼, 그리고 지나가는 걸음

세 번째 답은 사람들이 가장 먼저 떠올리는 것입니다. 다른 플레이어가 온라인이면 같은 광장에서 움직이는 모습을 보여 주는 것입니다. 작은 2D 소셜 월드 두 곳, Habbo와 Club Penguin은 격자 위 존재감의 양 끝을 보여 줍니다. 두 회사 모두 빈도를 공개하지 않았습니다. 제 손에 있는 것은 원래 클라이언트와 호환되도록 작성된 오픈 소스 팬 서버 두 개와 팬 클라이언트 하나입니다. 따라서 아래 수치는 그 클라이언트들이 무엇을 기대했는지에 대한 증거이지, Sulake나 Disney가 공개한 수치가 아닙니다.154849

Habbo: 서버 주기마다 한 걸음

Habbo 서버를 오픈 소스로 다시 구현한 Arcturus Morningstar는 각 방을 500ms 고정 주기로 돌리고(scheduleAtFixedRate(this, 500, 500, TimeUnit.MILLISECONDS)), 방의 주기마다 걷고 있는 각 유닛의 cycle을 한 번 호출합니다. IDLE_CYCLES = 240, 즉 120초에 아바타를 대기 상태로 표시하고, IDLE_CYCLES_KICK = 480, 즉 240초에 방 주인이 아닌 사람을 내보냅니다.15 오픈 소스 Habbo 클라이언트인 Nitro는 방의 오브젝트를 500ms의 고정 DEFAULT_UPDATE_INTERVAL에 걸쳐 움직입니다. 서버에서 업데이트가 올 때마다 새 위치까지의 변화량을 잡고, 경과 시간을 500으로 나눈 비율로 선형 보간하며(끝에서 고정), 그다음 변화량을 0으로 되돌립니다.49 한 주기가 정확히 한 타일이라는 것은 둘을 함께 본 제 읽기(500ms 서버 주기를 500ms 선형 보간으로 그림)이며, 서버의 RoomUnit을 읽어 확인하지는 않았습니다. 이 읽기가 맞다면 그리기는 구조상 서버보다 한 주기 늦으므로, 서버가 보조를 맞추는 한 데이터가 바닥나지 않습니다.

Club Penguin: 스트림이 아니라 목적지

Solero 프로젝트의 오픈 소스 Club Penguin 서버인 Houdini는 다른 설계를 보여 줍니다. 이동은 목적지 x와 y를 담은 sp 메시지 하나로, 방에 그대로 중계되고 클라이언트가 직접 그곳까지 걸어갑니다. 세이프 채팅 한 줄은 숫자를 담은 ss 메시지 하나이고, 다른 종류의 정해진 말도 같은 방식으로 오갑니다. 농담은 sj, 마스코트의 메시지는 sma, 무대 대사는 sl, 투어 가이드의 대사는 sg, 이모트는 se로, 각각 ID로 중계됩니다.48 서버는 이모트와 동작을 1초에 한 번, 프레임 변경을 0.5초에 한 번으로 제한하며, 하트비트 작업이 61초마다 연결된 클라이언트를 확인해 그동안 하트비트를 보내지 않은 클라이언트를 끊습니다.48 이동은 타일 단위가 아니라 클릭한 목적지로 가는 방식이고, 말은 상대편에서 찾아보는 번호입니다. 제가 읽기로는 그래서 단어 필터가 말해진 것 대부분을 볼 필요가 없었습니다.

어느 세계도 아래 물리 시연처럼 초당 10~60회 업데이트를 보내지 않습니다.16 사람들이 타일에서 타일로 걷는 세계에서는 한 걸음, 또는 목적지가 곧 업데이트입니다.

Gaffer on Games: 늦게, 알려진 두 점 사이를 그리기

클라이언트 쪽을 지배하는 방법은 Glenn Fiedler의 “Snapshot Interpolation”(Gaffer on Games, 2014년 11월 30일)에 정리되어 있습니다. 도착한 것을 버퍼에 담고, 지연된 두 업데이트 사이를 그립니다. “In effect, we’ve traded a small amount of added latency for smoothness.”(사실상 약간의 지연을 더하는 대신 부드러움을 얻은 것이다.)16 지연에 대한 그의 경험칙은 “enough delay so that I can lose two packets in a row and still have something to interpolate towards”(패킷을 연달아 두 개 잃어도 여전히 보간할 대상이 남을 만큼의 지연)입니다. 패킷 손실이 2~5%일 때 가장 잘 통하는 것은 “is 3X the packet send rate. At 10 packets per-second this is 300ms”(패킷 전송 간격의 3배. 초당 10패킷이면 300ms)이고, 여기에 지터를 위한 한두 프레임을 더해 그의 시연은 350ms로 돌아갔습니다. “30 snapshots per-second”(초당 30 스냅샷)에서는 같은 보호에 150ms가 들고, “60 packets per-second needs only 85ms.”(초당 60패킷이면 85ms면 된다)입니다.16 앞을 내다보고 추측하는 외삽은 “doesn’t work very well for rigid bodies because their motion is non-linear and unpredictable.”(강체의 움직임은 비선형이고 예측할 수 없어서 잘 통하지 않는다)라고 합니다.16

Kiradex의 광장은 WebSocket으로 FastAPI 서버에 연결되며,23 WebSocket은 TCP 위에서 돌아갑니다. TCP에서는 아무것도 잃지 않지만, 재전송된 패킷은 늦게 도착합니다. 제가 읽기로는 그래서 WebSocket 세계의 예산은 손실이 아니라 지터가 되고, 격자 위를 걷는 사람은 Fiedler의 방법에 쉬운 사례가 됩니다. 격자 위를 걷는 사람은 타일 중심 사이를 직선으로 움직이고 딱 멈추므로, 외삽할 것도, 숨길 비선형 움직임도 없습니다.

지나가는 걸음의 모델

Kiradex의 버퍼 크기를 정하려고, 10월 5일에 읽은 앱 자체 리그의 로직으로 다른 수집가가 옆을 걸어 지나가는 모습을 시뮬레이션했습니다. 스크립트 measure_remote_walk.py는 두 가지를 모델링합니다. 하나는 리그 자체의 산술로 본 현재 동작입니다. 걸음의 progress는 32비트 Float이고, 매 프레임 Float(deltaTime)에 초당 4타일을 곱한 만큼 나아갑니다. 이동이 도착할 때마다 걷는 사람의 마지막 정수 타일에서 경로를 다시 짜고, 진행 중인 걸음을 걸음 도중이라도 0으로 초기화합니다. 6타일보다 긴 경로는 점프가 됩니다. 그리고 걷는 사람은 정수 픽셀 위에 그려집니다. 리그는 다른 수집가를 모두 걷기의 초당 4타일로 그립니다. 달리기로 표시되는 것은 플레이어 자신의 걷기뿐이기 때문입니다. 다른 하나는 대안입니다. 일정한 렌더 시계, 즉 추정 서버 시각에서 100~350ms의 지연을 뺀 시계 위에서 스냅샷 보간을 하며, 렌더 시계를 사이에 두는 스탬프를 가진, 받은 두 업데이트 사이를 그립니다.750 현재 리그의 경우, 보내는 쪽은 현재 측정한 빈도로 완료된 타일마다 이동 하나를 보냅니다. 초당 4타일로 걸으면 250ms마다, 초당 6.4타일로 달리면 156ms마다입니다. 6.4는 달리기의 명목 빈도입니다(이 시리즈의 움직임 편 글은 각 타일의 초과분이 버려지기 때문에 60Hz에서 앱의 달리기를 초당 6.0타일로 모델링합니다). 버퍼의 경우, 보내는 쪽은 시리즈의 움직임 계약, 즉 움직임 편 브리프의 60Hz에서 걷기 타일당 16틱, 달리기 8틱, 초당 3.75타일과 7.5타일로도 움직입니다. 7절이 목표로 삼는 것이 이것입니다. 네트워크는 0, 30, 80ms의 균등한 지터를 더하고, 프레임은 10초 동안의 걷기 내내 초당 60회 그려집니다.72122

산술이 중요합니다. 32비트에서는 1/60초 프레임 15개를 초당 4타일로 더하면 정확히 1.0이 되므로, 한 걸음은 15프레임, 즉 보내는 쪽의 250ms와 정확히 같습니다. 64비트 산술에서는 같은 합이 0.9999999999999999가 되어 한 걸음에 16프레임이 걸리고, 그렇게 작성한 모델은 리그 자체의 산술에서는 생기지 않는, 꾸준한 걷기에서의 되돌림을 찾아내게 됩니다.7

이것은 모델이며, 그 가정들이야말로 수치를 결과가 아니라 크기의 길잡이로 다뤄야 하는 이유입니다. 곧고 탁 트인 길을 가정하므로 경로 길이는 맨해튼 거리입니다. 리그에서 √2배의 시간이 걸리는 대각선 걸음은 모델링하지 않았습니다. 지터는 균등합니다. 모든 프레임의 deltaTime은 정확히 1/60초이지만, 휴대폰에서는 달라집니다. 32비트 연산은 코드의 순서대로, 융합 곱셈-덧셈 없이 실행합니다. 두 기기에서 캡처한 것은 아무것도 없습니다. 그러니 지터 없는 꾸준한 걷기가 휴대폰에서 한 번도 되돌려지지 않는지는 이 모델로 결론 낼 수 없는 질문입니다. 1/60에서 아주 조금 벗어난 프레임 시간 때문에 한 걸음이 반올림 한 번만큼 1.0에 못 미칠 수 있기 때문입니다. 걸음 도중에 이동이 도착할 때마다 일어나는 초기화 자체는 모델의 결과가 아니라 소스를 읽은 것입니다. 버퍼 쪽은 결과가 기대는 두 가지도 가정합니다. 렌더러의 서버 시계 추정이 정확하다는 것, 그리고 업데이트가 도착하는 데 지터 말고는 시간이 걸리지 않는다는 것입니다. 실제 클라이언트는 서버의 시계를 추정해야 하며, 그 추정의 오차나 고려하지 않은 일정한 단방향 지연은 그대로 버퍼에서 빠져나갑니다.7

여섯 가지 경우(걷기와 달리기, 각각 지터 0, 30, 80ms)에 걸친 묶은 막대그래프 두 패널. 왼쪽은 현재 리그로, 보내는 쪽은 걷기 초당 4타일, 달리기 명목 6.4: 걷기에서 뒤로 움직이게 그려진 프레임은 0, 7, 16, 달리기 세 경우는 모두 45. 오른쪽은 버퍼를 쓴 경우로, 움직임 계약의 초당 3.75타일과 7.5타일. 600프레임 중 그릴 대상이 없는 프레임: 걷기는 100ms 지연에서 355, 400, 456, 300ms에서 0, 0, 27, 325ms에서 0, 0, 6, 350ms에서 0. 달리기는 100ms에서 125, 188, 304, 300ms 이상에서 0. 버퍼를 쓴 모든 실행은 뒤로 가는 프레임이 0.

모델에서 현재 리그는 걸음 도중에 이동이 도착하면 지나가는 수집가를 뒤로 움직이게 그립니다. 달리기에서는 모든 실행에서, 걷기에서는 지터가 있으면 언제나 그렇습니다. 움직임 계약의 속도에서 350ms 버퍼는 결코 그러지 않고, 데이터도 바닥나지 않습니다.26

현재 리그는 지터 없이 걸으면 걷는 사람을 깔끔하게 그립니다. 각 걸음은 다음 이동이 도착하기 한 프레임 전에 끝나므로, 40타일 동안 되돌림 0, 점프 0, 뒤로 가는 프레임 0입니다. 지터가 이것을 깨뜨립니다. 30ms에서는 이동 13개가 걸음 도중에 도착해 초기화를 일으키고, 그중 7개는 걸음 후반에 도착해 걷는 사람을 뒤로 그리며, 1개는 경로가 길어져 점프합니다. 80ms에서는 초기화 25번, 뒤로 가는 프레임 16개, 점프 2번입니다. 달리기에서는 보내는 쪽이 리그가 그리는 초당 4타일을 앞지르므로, 시험한 모든 지터에서 이동 64개 중 45개가 걸음 도중에 도착해 매번 뒤로 가는 프레임을 그리고, 점프는 9번입니다. 되돌림이 일어날 때마다 그려진 걷는 사람은 보내는 쪽보다 최대 5.9~6.7타일 뒤처지고, 그다음 점프로 앞으로 나옵니다. 꾸준한 걷기에서는 한 타일만큼 뒤처지는 일이 없습니다(최대 0.94).7 되돌림은 걸음에서 이미 그린 만큼 걷는 사람을 뒤로 끌어당겨 그리며, 이번 실행들에서는 16픽셀 타일에서 최대 14픽셀이었습니다.750

버퍼가 있으면 걷는 사람은 결코 뒤로 움직이지 않습니다. 버퍼를 쓴 60번의 실행(속도 4가지 × 지터 3가지 × 지연 5가지) 모두에서 뒤로 가는 프레임은 0입니다. 문제는 렌더러가 그릴 대상이 없어지는 빈도뿐이며, 보내는 쪽이 아직 전송하는 동안 그려지는 600프레임에 대해 셉니다. 움직임 계약의 걷기인 초당 3.75타일에서, 300ms 늦은 렌더 시계는 지터 0, 30, 80ms에서 0, 0, 27프레임이 바닥납니다. 325ms에서는 0, 0, 6, 350ms에서는 하나도 없습니다. 100ms 늦으면 이 속도들의 모든 경우에서 125~456프레임이 바닥납니다. 7.5로 달리면 300ms 이상에서는 시험한 모든 지터에서 0프레임입니다. 현재의 초당 4타일과 6.4타일에서는 같은 모델이 걷기 300ms에서 0, 0, 7이고, 325ms와 350ms에서는 0입니다.7 제가 읽기로는 추정 서버 시각보다 350ms 늦은 렌더 시계가 답이며, 그 이유는 센 결과가 아니라 상한에 있습니다. 타일마다 이동이 하나라면, 어떤 프레임에 필요한 업데이트의 스탬프는 렌더 시계보다 많아야 한 걸음 뒤이고, 그 스탬프보다 많아야 최악의 지터만큼 늦게 도착합니다. 초당 3.75타일에서 한 걸음은 1/3.75초, 266.7ms이고, 한 걸음에 80ms 지터를 더하면 346.7ms입니다. 따라서 시계가 정확하다면 350ms 지연은 80ms까지의 어떤 지터 추첨에서도 바닥나는 프레임이 없으며, 3.3ms의 여유가 있습니다.7 그 3ms가 시계 추정에 허용되는 전부입니다. 추정이 서버 시계보다 약 3ms 넘게 앞서 가면 보장이 사라지고 프레임이 바닥날 수 있습니다. 위의 수치는 한 번의 지터 추첨(시드 1)에서 나온 것이며, 325ms는 그 추첨에서 7.4절이 정한 기준을 4프레임 차이로 충족했지만 모든 추첨에서 그런 것은 아닙니다. 시드 1~200으로 다시 돌려, 초당 3.75타일 걷기에 지터 80ms로 보면, 325ms는 200번 모두에서 프레임이 바닥났고 그중 8번은 10개를 넘었으며, 350ms는 어느 경우에도 바닥나지 않았습니다.7 350ms는 Fiedler 자신의 시연이 쓴 지연이기도 합니다. 타일마다 이동 하나라면 이는 걷기 한 걸음(초당 3.75타일에서 267ms)보다 조금 긴 지연이며, Fiedler의 “3X the packet send rate”(패킷 전송 간격의 3배)를, 패킷이 곧 걸음이고 TCP 위에서는 잃어버리는 것이 아니라 늦게 도착하는 세계에 맞게 뒤집은 것입니다.16

4. 기법을, 숫자가 붙은 규칙으로

아래의 각 규칙은 위의 측정이나 인용한 출처로 거슬러 올라갈 수 있습니다. 규칙이 여러 출처를 종합한 제 정리인 경우, 출처 열에 어느 것인지 밝혀 두었습니다.

요소 규칙 출처
누가 움직이는가 늘 있는 마을 사람 중 약 열에 여섯은 기본 이동 유형이 정지형으로, 자기 자리에 서서 한 방향을 보거나 주위를 둘러보고, 열에 넷 가까이는 자기 자리 주변 한두 셀 범위를 돌아다닙니다. 드나드는 스토리 배우는 대부분 서 있으므로, 전체로는 열에 일곱과 넷에 하나가 됩니다. 숨김 플래그가 없는 Emerald의 100명: 59명이 서 있고, 39명이 돌아다니며, 2명이 제자리걸음. 158명 전체: 113명과 43명. 범위는 1~2셀1
속도 마을 사람은 플레이어의 속도로 한 걸음 내딛고, 그 걸음에 걸린 시간보다 오래 기다립니다. 268ms 걸음 하나 뒤에 0.54, 1.07, 1.61, 2.14초 중 하나를 무작위로. 더 느린 산책도 통합니다(놀러오세요 동물의 숲). 어느 쪽도 아닌 속도는 통하지 않습니다. Emerald의 공통 걷기 동작과 대기 테이블1, 놀러오세요 동물의 숲32
움직이는 비율 돌아다니는 사람이 움직이는 시간은 내딛은 걸음 하나 기준으로 많아야 11~33퍼센트이며, 막힌 걸음은 방향만 돌리고 기다립니다. Emerald, 위 두 줄에 대한 산수. 상한127
인구 6~10명이면 마을이 됩니다. 동물의 숲의 마을 전체도 최대 8~15명입니다. 미로마을 6명, 등화시티 7명, 보라시티 10명, 그중 숨김 플래그가 없는 사람은 2명, 3명, 6명1. 동물의 숲 마을은 최대 8~15명32
변화 일부 마을 사람은 플레이어의 진행에 따라 플래그로 떠나고 나타납니다. 세 마을의 23명 중 12명이 숨김 플래그를 가짐1
일과 하루는 시각이 붙은 지점 3~7개입니다. 어떤 하루를 적용할지는 키가 고르며, 구체적인 순서대로 시도해 가장 먼저 일치한 것이 채택됩니다. Abigail의 수요일 3지점, Pierre의 평범한 날 7지점. 키 형식 22개92
하루 하루는 플레이어가 돌아올 때 경과 일수를 받는 실행 한 번으로 정산합니다. 아무도 플레이하지 않는 동안 타이머를 돌리지 않습니다. UpdatePerDay3, Stardew의 첫 하루 업데이트9
기억 그날의 첫 대화는 마을 사람 한 명당 플래그 하나로 기억합니다. 보상은 작게 유지합니다. 모여봐요 동물의 숲에서 255 중 +111, Stardew에서 하트당 250 대비 +2010
새 단어 새로운 표현은 하루에 하나, 또는 만남 한 번에 하나씩 배급합니다. 리액션은 하루에 하나12, 유행어는 유행을 좇는 사람이 올 때마다 하나35
흔적 다른 사람의 흔적은 작은 고정 슬롯에 담기고, 썩거나 순환합니다. 기지 20개와 유행 5개4, 가게 8곳13, 수비 6마리47, 시간비14, 12일에서 약 50.8일에 걸친 유행의 오르내림38
대역 사람이 비운 슬롯은 대역이 채우고, 그 가치는 덜합니다. 조인 애비뉴의 팬은 75%, Serebii의 설명13
낯선 사람 낯선 사람끼리는 말이 아니라 존재감을 나눕니다. 이름 없이, 차임 소리나 정해진 문구만. Journey5, Dark Souls41, 아이들을 위한 Game Center17
말 정해진 대사는 ID로 전송되며, 인사만이 아니라 대답도 합니다. 완전한 문장으로는 단어 목록이 철자로 만들 수 있는 것을 하나도 만들 수 없습니다. Club Penguin의 ss ID48, 간이 회화의 열린 940단어와 2~10칸 격자38
업데이트 격자 위에서는 걸음마다 업데이트 하나. 원격으로 걷는 사람은 추정 서버 시각보다 걷기 한 걸음을 조금 넘게 늦은 시계로 그리고, 진행 중인 걸음은 결코 초기화하지 않습니다. Habbo의 500ms 주기와 선형 보간(에뮬레이터와 팬 클라이언트)1549. 초당 3.75타일과 7.5타일에서의 모델, 정확한 시계 동기화와 기본 지연 없음을 가정: 350ms 늦으면 지터 80ms까지 뒤로 가는 프레임 0, 바닥나는 프레임 07
대기 상태 입력 없이 2분이 지난 플레이어를 대기 상태로 표시하고, 하트비트에 응답하지 않게 된 연결은 닫습니다. Arcturus: 240주기(120초)에 대기, 대기 중인 방 주인 아닌 사람을 480주기(240초)에 내보냄15. Houdini는 61초 창 동안 하트비트를 보내지 않은 클라이언트를 끊음48. 브리프의 “핑 없이 240초”는 이 둘을 합친 것으로, 제 각색
제한 Game Center가 멀티플레이어 제한(친구만 포함)을 보고하면 독자적인 멀티플레이어는 빠집니다. 제가 읽기로는 진짜 플레이어 걸음의 재생도 포함되며, 그런 플레이어에게는 마을 사람만 보입니다. isMultiplayerGamingRestricted에 관한 Apple의 문서18. 메아리를 멀티플레이어로 보는 것은 제 읽기(7.6절)
소통 Game Center가 개인화된 소통의 제한, 또는 플레이어가 미성년자임을 보고하면 독자적인 소통은 빠집니다. isPersonalizedCommunicationRestricted에 관한 Apple의 문서51
보존 아이들의 데이터는 서면에 적은 목적에 필요한 기간만 보관하고, 삭제 기한을 고지에 적습니다. 개정된 16 CFR 312.10, 2026년 4월 22일부터 준수19

5. Apple의 방식: Game Center의 플래그, 연령대, 가이드라인, COPPA

아이들이 걸어 들어올 수 있는 마을은 Apple과 미국 연방거래위원회(FTC)의 규칙에 묶이며, 그 규칙 대부분에는 날짜가 있습니다. 이 절은 Apple과 FTC 자신의 말을, Apple이 공개한 경우에는 지원 버전과 함께 제시합니다. 어떤 규칙이 Kiradex에 무엇을 뜻하는지에 대해 제가 하는 말은 모두 읽기이며 그렇게 표시해 두었고, 그 어느 것도 법률 자문이 아닙니다.

Game Center의 세 가지 제한 플래그

Game Center의 로컬 플레이어는 게임이 무언가 소셜한 것을 열기 전에 읽을 수 있는 불리언 세 개를 가지고 있습니다. 지원 버전은 2026년 10월 4일에 저장한 Apple 문서에서 가져왔고, 각 설명은 원문 그대로 인용했습니다.185152

속성 iOS Apple의 설명
GKLocalPlayer.isMultiplayerGamingRestricted 13.0 “If this property is true, the local player can’t join multiplayer games. If your game uses a custom multiplayer feature, you should disable it.”(이 속성이 true이면 로컬 플레이어는 멀티플레이어 게임에 참여할 수 없다. 게임이 독자적인 멀티플레이어 기능을 쓴다면 그것을 비활성화해야 한다.)
GKLocalPlayer.isPersonalizedCommunicationRestricted 14.0 “If this property or the underage property is true, the local player can’t include personalized messages on invitations or enable voice communication in multiplayer games. If your game includes any custom communication features, you should disable them.”(이 속성이나 underage 속성이 true이면 로컬 플레이어는 초대에 개인화된 메시지를 넣거나 멀티플레이어 게임에서 음성 통신을 켤 수 없다. 게임에 독자적인 소통 기능이 있다면 그것을 비활성화해야 한다.)
GKLocalPlayer.isUnderage 4.1 “If this property is true, Game Center disables some features for the local player.”(이 속성이 true이면 Game Center는 로컬 플레이어의 일부 기능을 비활성화한다.)

첫 번째 플래그의 페이지는 가장 중요한 세부를 덧붙입니다. 값은 Screen Time에서 오며, Screen Time에서 보호자는 멀티플레이어 게임을 모든 사람과, 친구하고만, 아무와도 하지 않도록 허용할 수 있고, “when you configure the setting to friends only, this property returns true for restricted.”(설정을 친구만으로 지정하면 이 속성은 제한됨으로 true를 반환한다)입니다.18 그러니 친구하고만 놀도록 허락받은 아이도 제한된 것으로 읽히며, 자체 광장이 있는 게임은 그것을 끄라는 말을 듣습니다. 두 번째 플래그의 페이지에는 미치는 범위가 다른 두 문장이 있습니다. 첫 문장은 Game Center 자신이 막는 것, 즉 초대의 개인화된 메시지와 음성을 꼽습니다. 둘째 문장은 게임에게 게임 자체의 “any custom communication features”(모든 독자적인 소통 기능)를 비활성화하라고 요구하며, 정해진 것에 대한 예외는 없습니다. 제가 읽기로는 완전한 정해진 문장의 메뉴도 독자적인 소통 기능이며, 브리프는 그것을 그렇게 다룹니다.51

아이들을 위한 Game Center와 Screen Time

Apple의 Game Center 개인정보 페이지는 Kiradex가 이미 모두에게 채택한 규칙을 정합니다. “when communicating with other players in Game Center, children cannot send or receive user-inputted text. They are restricted to sending and receiving preset messages. In-game voice chat is disabled for children.”(Game Center에서 다른 플레이어와 소통할 때 아이들은 사용자가 입력한 텍스트를 보내거나 받을 수 없다. 정해진 메시지를 보내고 받는 것으로 제한된다. 게임 내 음성 채팅은 아이들에게 비활성화된다.)17 또한 “Children’s accounts never make real names visible to friends”(아이 계정은 실명을 친구에게 결코 보여 주지 않는다)라는 것과, 보호자가 Screen Time으로 “multiplayer functionality, the ability to add friends, and the ability to connect with friends playing the same game”(멀티플레이어 기능, 친구를 추가하는 기능, 같은 게임을 하는 친구와 연결하는 기능)을 차단할 수 있다는 것도 말합니다.17 Apple의 Screen Time 지원 페이지는 Game Center 제한을 이름으로 나열하며, 그중에는 “Connect with Friends”(친구와 연결)와 “Private Messaging”(개인 메시지)이 있고 각각 “Allowed or Blocked”(허용 또는 차단)입니다.53

Declared Age Range

2025년 6월 11일, Apple은 자녀 계정이 “is required for children under 13”(13세 미만 아이에게 필수)이 되며 “App developers will be able to request this information through the new Declared Age Range API”(앱 개발자는 새로운 Declared Age Range API를 통해 이 정보를 요청할 수 있게 된다)라고 발표했습니다. 보호자는 아이의 연령대를 “always, for each app request, or never”(항상, 앱이 요청할 때마다, 또는 공유하지 않음) 중에서 고르고, 이는 “in a way that does not reveal the child’s birth date”(아이의 생년월일을 드러내지 않는 방식으로) 공유됩니다.54 같은 발표는 연령 등급이 그해 말까지 “expanded to five categories”(다섯 범주로 확대)되어 청소년을 위해 13+, 16+, 18+가 생긴다고 했습니다.54 Kiradex의 월드 계획은 이 API를 주니어 규칙의 스위치로 지목하고 10월 2일의 결정을 기록해 두었습니다. 신고된 연령대로 13세 미만이면 광장이 아예 없고, 13~15세면 주니어 규칙이 적용된 광장, 16세 이상이거나 신고하지 않았으면 완전한 광장입니다.23 이 글을 위해 API 자체의 레퍼런스 페이지는 가져오지 않았으므로, 최소 iOS 버전은 여기서 밝히지 않습니다.

App Review 가이드라인

소셜 광장을 묶는 가이드라인은 세 가지이며, Apple이 “Last Updated: June 8, 2026”(최종 업데이트: 2026년 6월 8일)이라고 날짜를 적은 판에서 인용합니다.55

  • 1.2, 사용자 생성 콘텐츠. “with user-generated content or social networking services must include”(사용자 생성 콘텐츠나 소셜 네트워킹 서비스가 있는 앱은 다음을 갖춰야 한다)로서, 불쾌한 자료를 거르는 방법, “A mechanism to report offensive content and timely responses to concerns”(불쾌한 콘텐츠를 신고하는 장치와 우려에 대한 시의적절한 대응), “The ability to block abusive users from the service”(악성 사용자를 서비스에서 차단하는 기능), “Published contact information so users can easily reach you”(사용자가 쉽게 연락할 수 있도록 공개된 연락처)를 꼽습니다. 결국 “used primarily for pornographic content, Chatroulette-style experiences, random or anonymous chat”(주로 음란물, Chatroulette 방식의 경험, 무작위 또는 익명 채팅에 쓰이게 된) 앱과 그 목록의 나머지는 “do not belong on the App Store and may be removed without notice”(App Store에 어울리지 않으며 예고 없이 삭제될 수 있다)라고 합니다.55
  • 1.3, 어린이 카테고리. 이 카테고리의 앱은 “must not include links out of the app, purchasing opportunities, or other distractions to kids unless reserved for a designated area behind a parental gate”(보호자 게이트 뒤의 지정된 영역에 한정하지 않는 한, 앱 밖으로 나가는 링크, 구매 기회, 아이들의 주의를 흩뜨리는 다른 것을 넣어서는 안 된다)이고, 좁은 예외를 빼면 “should not include third-party analytics or third-party advertising”(서드파티 분석이나 서드파티 광고를 넣어서는 안 된다)입니다.55
  • 5.1.4, 아이들. “collect, transmit, or have the capability to share personal information (e.g. name, address, email, location, photos, videos, drawings, the ability to chat, other personal data, or persistent identifiers used in combination with any of the above) from a minor must include a privacy policy and must comply with all applicable children’s privacy statutes”(미성년자의 개인정보(예: 이름, 주소, 이메일, 위치, 사진, 동영상, 그림, 채팅 기능, 기타 개인 데이터, 또는 이들 중 어느 것과 결합해 쓰이는 영구 식별자)를 수집하거나 전송하거나 공유할 수 있는 앱은 개인정보 처리방침을 갖추고 적용되는 모든 아동 개인정보 법규를 준수해야 한다)이며, “the parental gate requirement for the Kid’s Category is generally not the same as securing parental consent”(어린이 카테고리의 보호자 게이트 요건은 일반적으로 보호자 동의를 받는 것과 같지 않다)입니다.55

Kiradex의 계획은 설계 문서가 가이드라인에서 끌어낸 두 갈래 길 중 두 번째, 즉 어린이 카테고리 밖에서 9+ 또는 12+ 등급(등급은 아직 정하지 않았습니다)으로 5.1.4를 따르는 길을 택합니다.23 제가 읽기로는 5.1.4 목록의 “the ability to chat”(채팅 기능)은 정해진 대사만 있는 광장이라도, 무엇을 피하든 개인정보 처리방침은 필요하다는 뜻입니다.

개정된 COPPA

FTC의 개정 아동 온라인 개인정보 보호 규칙(Children’s Online Privacy Protection Rule)은 2025년 4월 22일 연방 관보(Federal Register)에 게재되었습니다(문서 2025-05904, 90 FR 16918). 이 규칙은 “is effective June 23, 2025”(2025년 6월 23일에 발효된다)이며, “Except with respect to § 312.11(d)(1), (d)(4), and (g), regulated entities have until April 22, 2026 to comply.”(§ 312.11(d)(1), (d)(4), (g)를 제외하고 규제 대상은 2026년 4월 22일까지 준수해야 한다)입니다.19 5대 0 표결을 알린 FTC의 2025년 1월 16일 보도자료는 변경 사항을 이렇게 요약했습니다. “related to targeted advertising or other purposes”(맞춤형 광고나 기타 목적과 관련된) 제3자에게 아이들의 정보를 공개하려면 별도의 검증 가능한 보호자 동의가 필요하고, 보존은 “for as long as reasonably necessary to fulfill a specific purpose”(특정 목적을 이루는 데 합리적으로 필요한 기간)로 한정되어 “operators cannot retain the information indefinitely”(운영자는 정보를 무기한 보관할 수 없다)입니다.56

규칙 본문에서 무언가를 기억하는 광장과 관련된 대목은 세 곳입니다.

  • 보존, § 312.10. “Personal information collected online from a child may not be retained indefinitely. At a minimum, the operator must establish, implement, and maintain a written data retention policy that sets forth the purposes for which children’s personal information is collected, the business need for retaining such information, and a timeframe for deletion of such information.”(아이에게서 온라인으로 수집한 개인정보는 무기한 보관해서는 안 된다. 운영자는 최소한 아이들의 개인정보를 수집하는 목적, 그 정보를 보관할 업무상 필요, 그 정보의 삭제 기한을 밝힌 서면 데이터 보존 정책을 수립하고 시행하고 유지해야 한다.) 이 정책은 온라인 고지에 들어갑니다.19
  • 영구 식별자, § 312.2. 개인정보에는 “A persistent identifier that can be used to recognize a user over time and across different websites or online services”(시간이 지나도, 그리고 서로 다른 웹사이트나 온라인 서비스에 걸쳐 사용자를 알아보는 데 쓰일 수 있는 영구 식별자)가 포함되며, 예로 “a customer number held in a cookie, an Internet Protocol (IP) address, a processor or device serial number, or unique device identifier”(쿠키에 담긴 고객 번호, IP 주소, 프로세서나 기기의 일련번호, 고유 기기 식별자)를 듭니다.19
  • 내부 운영, § 312.5(c)(7). “Where an operator collects a persistent identifier and no other personal information and such identifier is used for the sole purpose of providing support for the internal operations of the website or online service”(운영자가 영구 식별자만 수집하고 다른 개인정보는 수집하지 않으며, 그 식별자를 웹사이트나 온라인 서비스의 내부 운영을 지원하는 목적으로만 쓰는 경우)에는, 고지를 전제로 동의가 필요하지 않습니다.19

개정된 § 312.8은 아이들의 데이터에 대한 서면 정보 보안 프로그램도 요구합니다.19 방문자의 걸음을 재생하는 광장에 이것이 무엇을 뜻하는지에 대한 제 읽기는 브리프에 표시해 두었습니다. WORLD.md 3절은 최종 설계를 변호사에게 읽혀 달라고 요청하지만, 10월 2일의 결정은 앱이 자리를 잡을 때까지 그것을 미루었으므로, 그때까지는 이 읽기가 유지됩니다.23

비용

이 절의 어떤 것도 기기에서 측정하지 않았습니다. 인증 후 GKLocalPlayer의 불리언 세 개를 읽는 시간은 여기서 재지 않았고, 40명이 들어가는 방에서 최대 39명의 다른 수집가를 위한 350ms 렌더 버퍼의 메모리나 CPU도 마찬가지입니다.21 제가 읽기로는 어느 쪽도 한 프레임을 그리는 일에 비하면 문제가 되지 않겠지만, 그것은 읽기이며, 브리프의 검사는 시간이 아니라 동작을 봅니다.

6. 사례 연구: 자체 코드로 측정한 Kiradex의 광장

Kiradex World는 제 카드 수집 앱 Kiradex 안에 있는 픽셀 마을입니다. 16픽셀 타일로 그린 40×30 광장이며, 존재감을 메모리에만 두고 아무것도 저장하지 않는 작은 FastAPI와 WebSocket 서버가 돌립니다. 다만 그 웹 서버가 기본으로 남기는 로그는 예외입니다(7.6절, 설정 6).212357 10월 5일에 작업 트리에서 서버와 앱의 월드 코드를 아무것도 바꾸지 않고 읽었고, 스크립트 두 개로 서버 자체의 클래스를 시뮬레이션한 시계 위에서 돌렸습니다. 그러니 이 절의 수치는 제가 코드를 읽은 것이 아니라 코드의 동작입니다.21620 저장소는 비공개이므로 파일은 경로로 밝힙니다. 아래는 docs/WORLD.md의 계획, 코드 자체의 docstring, 그리고 코드가 실제로 하는 일 사이의 차이입니다. 어느 것도 누군가가 숨긴 결함이 아니며, 네 번째의 일부는 계획이 이미 아직 만들지 않은 것으로 꼽아 두었습니다.

있는 것

서버에는 정해진 대사가 23개 있습니다. 플레이어용 14개(인덱스 0~13)와 마을 자체 수집가용 9개(14~22)입니다. 방 정원은 40명. 플레이어마다 초당 이동 8회와 2.0초에 대사 한 번이라는 제한이 있습니다. 마을 시계는 0.5초마다 틱합니다(server/app/main.py의 TICK_SECONDS). 같은 수집가에게 카드를 보여 주는 데는 60초의 재사용 대기 시간이 있고, 40×30 광장의 도착 지점은 타일 (19, 13)입니다.2120 방 안의 모두가 같은 수집가를 보도록, 대본이 있는 수집가 네 명이 서버에 삽니다. Moss, Prism, Comet, Dawn이며, 각자 웨이포인트 4개를 가지고 고리 길이는 18, 22, 30, 32타일입니다. 각자 보고 싶어 하는 카드 종류(빈티지, 홀로, 아무거나, 일본어판)와 대사 2~3개를 가지고 있습니다. server/app/npcs.py는 STEP_SECONDS = 0.7로 정하고, 각 수집가를 웨이포인트에서 균등 분포 3~8초 동안 멈추게 하며, 균등 분포 20~40초마다(첫 대사는 8~20초 뒤에) 대사를 하게 합니다.2120

제가 읽기로는 이것은 올바른 직감입니다. 마을 사람이 각 휴대폰이 아니라 서버에 있고, 대사는 느린 시계로 나오며, 각자 바라는 것이 있어서 컬렉션이 의미를 가질 곳이 있습니다. 측정해 보면 그중 세 가지가 무너지고, 네 번째는 계획과 코드 사이의 틈입니다.

1. 걸음이 멈췄다 갔다 합니다

measure_npc_cadence.py는 server/app/npcs.py와 rooms.py를 가져와 광장과 네 수집가를 구성하고, 실제 0.5초 틱으로 시뮬레이션상 한 시간 동안 진행합니다.6 구간 안에서 걸음과 걸음 사이의 간격 6,253개는 모두 정확히 1.0초이지, 0.7초가 아닙니다. 걸음은 그 next_move 이후의 첫 틱에서 일어나므로, 0.7초는 두 틱으로 올림됩니다. 상수가 뜻하는 1.43이 아니라 초당 1.0타일입니다. 구간 전의 멈춤은 평균 6.75초(4.5~9.0, 1,202번)이고, 대사는 한 수집가가 연달아 하는 대사 사이의 간격 465개에서 평균 30.7초 간격입니다(한 시간에 대사 469개).6

클라이언트는 어떤 걸음이든 250ms에 그립니다. PlazaRig.swift가 101번째 줄에서 tilesPerSecond를 4로 정하기 때문입니다.50 그러니 초당 1타일로 걷는 수집가는 각 구간의 25퍼센트 동안만 움직이게 그려지고, 나머지 75퍼센트는 타일과 타일 사이마다 가만히 서 있습니다.6 Emerald의 돌아다니는 사람은 플레이어의 속도로 한 걸음 내딛고 기다립니다. Kiradex의 수집가들은 걷고 있어야 할 동안 플레이어의 속도로 한 타일 길이의 짧은 움직임을 되풀이하며, 제가 읽기로는 주민의 느긋한 걸음으로도, 보통의 걷기로도 보이지 않습니다(1절 타임라인 그림의 맨 아래 줄).

2. 옆을 걸어 지나가는 다른 수집가가 되돌려집니다

서버가 다른 수집가의 이동을 중계하면 PlazaRig.move(other:to:facing:)(PlazaRig.swift의 526~542번째 줄)는 걷는 사람의 마지막 정수 타일에서 경로를 짜고, 걷는 사람의 걸음들을 그 경로로 설정하며, 걸음이 반쯤 그려진 도중이라도 메시지마다 progress = 0으로 둡니다. 6타일보다 긴 경로는 점프입니다.50 3절의 모델이 리그 자체의 32비트 산술로 여기에 숫자를 붙입니다. 지터 없는 꾸준한 걷기는 각 걸음이 다음 이동이 도착하기 직전에 끝나므로 되돌려지지 않습니다. 지터가 30ms나 80ms이면 40타일 걷기는 걸음 도중에 13번이나 25번 초기화되고, 그중 7번이나 16번이 뒤로 그려집니다. 달리기에서는 리그가 걷는 사람을 걷기의 초당 4타일로 그리므로, 시험한 모든 지터에서 이동 64개 중 45개가 걸음 도중에 도착하고 점프는 9번이며, 되돌림이 일어날 때마다 그려진 걷는 사람은 보내는 쪽보다 최대 5.9~6.7타일 뒤처집니다.7 이 시리즈의 이전 글 픽셀 아트의 움직임은 플레이어 자신이 걸음 도중에 두 번째로 탭할 때 생기는 같은 되돌림을 코드에서 읽어 냅니다. 그 글의 브리프가 정한 규칙, 즉 진행 중인 걸음은 결코 초기화하지 않는다는 규칙을 이 브리프는 다른 수집가에게 적용합니다.22

3. 비면 마을이 멈춥니다

server/app/npcs.py의 모듈 docstring은 수집가들에 대해 “A town is never empty, and it has somewhere for a collection to matter.”(마을은 결코 비지 않으며, 컬렉션이 의미를 가질 곳이 있다)라고 말합니다.20 그런데 server/app/rooms.py의 Room.tick은 자체 docstring에서 “A room with nobody in it stands still”(아무도 없는 방은 가만히 있다)이라고 반대로 말하고, 실제로 그렇게 합니다. 방에 플레이어가 없으면 곧바로 반환하므로, 수집가들은 있던 자리에서 굳어 버립니다.206 스크립트는 그때 처음 들어온 사람이 무엇을 보는지 측정합니다. 새로 만든 수집가들을 한 번의 틱으로 시뮬레이션상 10,000초까지 진행시키면 수집가마다 하나씩 대사 네 개가 한꺼번에 나오고, 모두 서 있던 자리에 그대로 서 있습니다. 그 사이에는 아무 일도 없었습니다.6 게다가 서버는 아무것도 저장하지 않으므로, 조용한 아침에 처음 들어온 수집가는 같은 네 사람을 볼 뿐, 다른 누군가가 거기 있었다는 흔적은 아무것도 보지 못합니다. Kiradex/와 server/app/를 검색해도 방문이나 매일의 대화를 기록하는 것은 나오지 않습니다.24

4. 빈도, 그리고 Game Center의 플래그

두 가지 차이는 docs/WORLD.md와 코드 사이에 있습니다. 계획의 5절은 서버에 이르게 된 근거로, 클라이언트가 위치와 방향을 8~10Hz로 보내는 설계를 그대로 두고 있습니다.23 앱은 대신 완료된 타일마다 move를 하나 보냅니다. 현재 걷기에서 초당 4회, 달리기의 명목 빈도로 6.4회(움직임 편 글의 코드 모델에서는 60Hz에서 6.0회)이며, 어느 쪽이든 서버의 초당 8회 제한 아래입니다. 시리즈의 움직임 계약, 즉 60Hz에서 걷기 타일당 16틱, 달리기 8틱이면 3.75와 7.5가 되어 여전히 제한 아래입니다(7.4절).2122 제가 읽기로는 격자에는 타일 단위가 더 나은 설계이며 브리프는 이를 유지합니다. 고쳐야 할 것은 계획의 문장입니다.

계획의 3절은 신원을 Game Center 위에 세우는데, 그 이유는 “a child’s Game Center is controlled by the parent in Screen Time (multiplayer on/off, adding friends on/off, sharing the friend list with games on/off).”(아이의 Game Center는 보호자가 Screen Time에서 관리한다(멀티플레이어 켜기/끄기, 친구 추가 켜기/끄기, 친구 목록을 게임과 공유하기 켜기/끄기))이기 때문입니다.23 Kiradex/와 server/app/ 어디에도 GKLocalPlayer.isMultiplayerGamingRestricted, isUnderage, isPersonalizedCommunicationRestricted를 읽거나 Declared Age Range API를 호출하는 것은 없습니다.24 계획의 5절은 이미 아직 만들지 않은 것 가운데 “junior mode by Declared Age Range”(Declared Age Range에 따른 주니어 모드)를 꼽고 있습니다.23 꼽지 않은 것은 Game Center의 플래그입니다. Screen Time의 멀티플레이어 설정은 Game Center 자체의 멀티플레이어를 제어하고, Apple의 문서는 “a custom multiplayer feature”(독자적인 멀티플레이어 기능)가 있는 게임에게 스스로 그것을 비활성화하라고 요구하므로, 앱이 그 플래그를 읽기 전까지는 보호자의 설정이 광장에 닿지 않습니다.18

클라이언트는 소켓이 끊기면 min(30, 2^attempts)초의 백오프로 다시 연결하며(PlazaClient.swift 165번째 줄), 수집가를 숨기는 일은 기기에서 ID로 이루어집니다.215023 브리프에서는 둘 다 그대로 두지만, 숨김 목록의 쓰임새를 하나 더합니다. 광장의 메아리를 요청할 때마다 숨김 목록을 함께 보내, 이 플레이어가 숨긴 수집가들의 걸음을 서버가 빼고 보낼 수 있게 합니다(7.2절).

7. 브리프: Kiradex가 만들 것과 통과해야 할 검사

브리프는 광장에 이미 있는 것(서버 자체의 수집가, 정해진 대사만, 타일마다 이동 하나, 메모리 속 존재감)을 유지하고, 원전이 제시하는 순서대로 세 가지 답을 더합니다. 삶이 멈추지 않는 마을 사람, 그곳에 있었던 사람들의 흔적, 그리고 되돌려지지 않고 그려지는 존재감입니다. 모든 항목에는 스크립트, 테스트, 캡처 중 하나로 통과와 실패를 가릴 수 있는 검사가 있습니다. 타일 좌표는 광장의 것으로, x는 오른쪽, y는 아래쪽입니다.21

빌려 오는 것에 대한 선은 이 시리즈의 첫 번째 글이 특허와 표현에 대해 그은 선과 같습니다. 생물은 없고, Pokémon의 지명도 없으며, 프랜차이즈 단어를 기능 이름으로 쓰지 않고, 조준해서 던져 잡는 것, 포획 가능성을 보여 주는 것, 동료를 싸우게 보내는 것, 무언가에 올라타는 것은 아무것도 없습니다.36 아래의 모든 요소는 걷고, 서 있고, 정해진 완전한 문장을 말하는 마을 사람들, 그리고 다른 수집가들의 걸음을 흐릿하게 재생한 것입니다. 게임의 시스템은 위에서 출간된 작품에 관한 사실로서 이름을 들었습니다. Kiradex의 기능에는 여기서 Kiradex 자신의 이름을 붙입니다. 마을 사람, 메아리, 오늘의 인사, 대사집, 그리고 조용한 광장입니다. docs/WORLD.md 3절은 이미 친구가 아닌 실시간 수집가를 모두 행인이라고 부릅니다. 그 표현은 그대로 두고, 재생은 메아리라고 불러 둘이 결코 헷갈리지 않게 합니다.23 법에 관한 메모는 읽기이며, 자문이 아닙니다.

7.1 일과를 가진 마을 사람

  • 인원. 광장 자체의 수집가를 4명에서 8명으로 늘리고, 각자에게 자리를 줍니다.21 구성은 Emerald의 늘 있는 사람들, 즉 100명 중 59명이 서 있고 41명은 그렇지 않은(39명이 돌아다니고 2명이 제자리걸음) 비율을 따르며, 여덟 명으로 반올림하면 다섯이 서 있고 셋이 움직입니다. 다섯은 자리에 서서 방향을 바꿉니다. 가게 계산대, 회관 문, 분수, 그리고 노점 두 곳입니다. 둘은 Emerald의 돌아다니는 사람처럼 자리 주변 2셀 범위를 돌아다닙니다. 움직이는 세 번째는 Emerald의 비율에서 온 것이 아닙니다. Emerald의 마을 사람 158명은 모두 서 있거나, 범위 안을 돌아다니거나, 제자리걸음을 하며, 정해진 경로를 걷는 사람은 없기 때문입니다. 세 번째는 현재의 웨이포인트 고리 네 개 중 하나를 남긴 것으로, 자리 사이를 한 바퀴 돕니다. 그러니 현재의 고리 넷은 하나가 됩니다.121 새 수집가 네 명은 지금의 네 명처럼 앱 자체의 인물 시스템으로 그리고, 앱 자체의 단어 목록에서 핸들을 받습니다.20
  • 대기. 자리에 있는 수집가는 방향을 돌리고, 돌아다니는 사람은 한 걸음 내딛는데, 그 전의 대기는 서버의 0.5초 시계로 1, 2, 3, 4틱 중 무작위로 고릅니다. 0.5, 1.0, 1.5, 2.0초로, Emerald의 0.54, 1.07, 1.61, 2.14를 틱 단위로 반올림한 값입니다. 정수 틱으로 된 대기는 틱에 의해 다시 반올림되지 않습니다. 그것이 바로 6절에서 걸음에 대해 측정한 결함입니다.16
  • 방의 시계. 모든 방은 시계 하나로 돌아갑니다. 서버의 UTC 시각을 방 자체의 시간대, 즉 서버 설정에서 방마다 하나씩 정한 IANA 시간대로 읽은 것입니다. 서버는 도착할 때 welcome과 조용한 광장의 응답({"zone": "<IANA name>", "clock": <server ms>})으로 둘을 모두 보내며, 광장이 열려 있는 동안 앱은 시간대를 휴대폰이 아니라 거기서 가져옵니다. 그러면 방 안의 모두가 같은 빛 아래에서 같은 마을 사람들을 봅니다. 현재 Daylight.now는 휴대폰 자체의 달력과 시간대인 Calendar.current에서 시각을 가져오고, 서버는 단조 증가 시계만 두며 어떤 시간대도 지정하지 않습니다.50 방이 어느 시간대를 쓰는지, 수집가가 어느 방으로 보내지는지는 이 브리프가 열어 두는 설정입니다. 모든 방에 시간대 하나를 쓰는 것이 가장 간단한 출발점입니다. 광장 밖에서는 휴대폰의 시계가 그대로 수집가의 시계입니다.
  • 일과. 마을 사람 각자의 하루는 서버 데이터에 문자열 하나로 적은 시각이 붙은 지점 3~7개이며, 차례로 시도하는 키로 고릅니다. 날짜, 요일, 시간대, 그리고 기본값 순이며, 모두 방의 시계로 읽습니다.9 시간대는 앱 자체의 것으로, Kiradex/World/Daylight.swift의 Daylight.Phase에서 가져옵니다. 아침은 6시부터 9시, 낮은 9시부터 17시, 저녁은 17시부터 20시, 그 밖은 밤입니다.50 방의 시계로 밤이 되면, 자리에 있는 다섯과 돌아다니는 둘은 Stardew Valley에서 Pierre의 금요일이 술집에서(“Returns home to sleep.”, 집으로 돌아가 잔다) 끝나듯 각자 자기 문으로 들어가고, 남긴 고리를 걷는 사람은 한 바퀴를 줄입니다.30
  • 속도. 멈췄다 갔다 하는 걸음을 두 가지 변경 중 하나로 고칩니다. 권장안은 서버가 구간을 한 번 {"t": "walk", "id": ..., "path": [...], "at": <server ms>}로 보내고, 클라이언트가 at부터 시리즈의 움직임 계약에 따른 플레이어의 걷기, 즉 60Hz에서 타일당 16틱, 초당 3.75타일(현재 리그에서는 4)로 걷게 하는 것입니다.22 최소안은 서버의 걸음 간격을 정수 틱으로 만드는 STEP_SECONDS = 0.5이며, 클라이언트는 마을 사람의 걸음을 같은 0.5초, 초당 2타일로 그립니다. 플레이어의 걷기가 아니라 주민의 느긋한 걸음입니다.632
  • 방이 비었을 때의 시간. 마을 사람의 타일은 방 시계의 순수 함수입니다. 경로, 시간대, 시드로부터 물을 때 계산하며, Room.tick으로 진행시키지 않습니다. 시드는 서버 설정에 있는 전역 값 하나로, 방이나 시간대마다 따로 두지 않으므로, 시간대가 같은 두 방은 같은 타일에 같은 마을 사람들을 보여 줍니다. 방의 시계로 07:40에 처음 들어온 수집가는 07:40에 Moss가 있어야 할 곳에서 Moss를 보며, 다른 시간대로 설정된 휴대폰으로 같은 방에 들어온 수집가도 마찬가지입니다.63

검사. (a) 설계에 따른 속도. 권장 구간의 경우, 클라이언트 테스트가 5타일 직선 경로와 at 스탬프를 가진 대본 walk를 넣고, 움직임 브리프의 -motionLog가 플레이어의 위치를 기록하듯 마을 사람의 위치를 틱마다 기록하며, 다음을 확인합니다. 클라이언트가 추정한 방의 시계로 at에 해당하는 틱에 출발하는지, 경로의 타일을 차례로 지나는지, 틱마다 정확히 1픽셀 움직이고 결코 뒤로 가지 않는지, 타일당 16틱이 걸리는지, 그리고 at 80틱 뒤에 마지막 타일에 서 있는지입니다. 서버 테스트는 각 구간이 스탬프와 함께 통째로 한 번만 전송되는지 확인합니다. 최소 대안의 경우, 서버 테스트가 0.5초 틱으로 마을 사람들을 시뮬레이션상 한 시간 동안 돌리고 구간 안의 모든 걸음 간격이 설정값과 같은지 확인합니다. 현재 measure_npc_cadence.py는 설정값 0.7초에 대해 1.0초를 찾아냅니다. 어느 쪽이든 서버 테스트는 모든 대기가 정수 틱인지 확인합니다.622 (b) 테스트가 시간대가 같은 두 방을 600초 간격으로 열고, 둘 다 하나의 전역 시드를 쓸 때 같은 서버 시각에 마을 사람들의 타일이 같은지 확인합니다. 휴대폰의 시간대를 방과 다른 시간대로 둔 클라이언트 테스트는 광장의 시간대가 방의 시계를 따르는지 확인합니다. (c) 테스트가 서버 시계를 방의 시간대로 03:00인 UTC 시각으로 맞추고, 자리에 있는 다섯과 돌아다니는 둘은 광장 맵에 없고 남긴 고리를 걷는 사람은 있는지 확인합니다. (d) 서버 테스트가 여덟 명을 종류별로 세어 자리 5명, 돌아다니는 사람 2명, 남긴 고리 1명인지 확인합니다.

7.2 메아리: 최근 여덟 번의 걸음

  • 방식. 광장은 최근 방문 8개를 보관합니다.413 방문 하나는 도착부터 처음 들어간 문까지, 또는 60초 중 짧은 쪽까지 걸은 경로를 타일과 시각의 쌍으로 담은 것입니다. 걷는 사람의 인물 모습은 보관하지 않습니다. 방에 있는 실시간 수집가가 8명보다 적으면 메아리가 그 빈자리를 채웁니다. 각 메아리는 자기 속도로 걸음을 재생하고, 들어간 문에서 사라집니다. 메아리에는 이름표가 없고, 카드를 들지 않으며, 탭 대상이 없고, 아무 말도 하지 않습니다. 사람이 아니라 걸음입니다.5
  • 모습. 메아리의 인물 모습은 결코 걷는 사람의 모습에서 파생하지 않습니다. 서버는 각 메아리에게 평범한 모습 네 가지 중 하나를 줍니다. 매니페스트의 기본 파츠를 네 가지 평범한 염색으로 물들인 것으로, 포지가 메아리용으로 만들고 흐릿하게(포지의 염색 램프 하나를 고정된 알파로) 그립니다. 넷 중 무엇을 고를지는 서버가 보관하고 결코 보내지 않는 비밀 키 아래에서 걸음 자체의 솔트를 키 해시한 값으로 정하므로, 같은 수집가의 두 걸음은 서로 무관한 모습을 받습니다. 수집가도 기본 파츠를 입을 수 있으므로, 우연히 넷 중 하나를 입은 사람이 자기 메아리와 우연히 일치할 수는 있지만, 그 일치는 아무것도 전하지 않습니다. 모습은 누가 걸었는지에 대해 아무것도 말하지 않습니다.58
  • 소멸. 메아리는 7일이 지나거나 더 새로운 방문 8개가 들어오면, 둘 중 먼저 오는 때에 지워집니다.14
  • 누가 메아리를 보는가. 실시간 광장에 있는 수집가로, 광장의 수집가가 8명보다 적을 때입니다. isMultiplayerGamingRestricted가 true인 사람은 보지 않습니다. 그 사람의 조용한 광장에는 마을 사람만 있습니다. 제가 읽기로는 메아리가 광장 멀티플레이어의 일부이기 때문입니다(7.6, 설정 1).18
  • 숨기기. 수집가를 숨기면 그 사람의 메아리도 숨겨지고, 거르는 일은 서버가 합니다. 저장된 걸음마다 무작위 솔트와 태그가 있습니다. 태그는 서버만 가진 비밀 키 아래에서 걷는 사람의 ID와 그 솔트를 키 해시(HMAC)한 것이므로, 같은 수집가의 두 걸음은 서로 무관한 태그를 가집니다. 메아리 요청에는 요청자가 숨긴 ID들이 실립니다. 서버는 저장된 걸음마다 숨긴 각 ID의 태그를 다시 계산해 일치하는 걸음을 빼고, 요청에 응답하면, 또는 실시간 연결이라면 소켓이 닫히면 그 목록을 버립니다. 클라이언트에 닿는 것은 평범한 모습, 경로, 그리고 시 단위의 시각뿐입니다. ID도, 태그도, 솔트도, 걷는 사람의 모습에서 가져온 것도 없습니다. 이로써 식별자와 외양이 제거되는데, 지금은 이 둘이 모든 도착에 함께 실려 갑니다(server/app/rooms.py의 Player.public()이 ID, 핸들, 모습을 객체 하나로 보내고, server/app/main.py가 그것을 방에 브로드캐스트합니다). 걸음 자체는 제거되지 않습니다. 경로, 그 속도, 그 시각은 그 시간에 광장에 있었던 사람이나 친구가 늘 어디로 가는지 아는 사람에게는 어떤 수집가를 가리킬 수 있으며, 같은 걸음이 있던 때 방에 있었던 클라이언트는 각 타일을 걷는 사람의 ID와 함께 moved로 받았습니다. 메아리는 이름이 없을 뿐, 익명이 아닙니다.2320
  • 무엇을 저장하는가, 그리고 COPPA에 대한 읽기. 핸들, Game Center ID, 기기 ID, 걷는 사람의 모습, 지역, 시보다 세밀한 시각은 저장하지 않습니다. 저장하는 것은 타일, 걸음 시작으로부터의 상대적인 걸음 시각, 시, 평범한 모습도 고르는 솔트, 그리고 숨김을 적용하는 태그입니다. 읽기이며, 자문이 아닙니다. 태그는 영구 식별자에서 파생되므로 저는 그것을 영구 식별자로 다룹니다. 태그는 서버에 머물고, 결코 전송되지 않으며, 숨김을 적용하는 목적으로만 쓰입니다. 저는 이것을 § 312.5(c)(7)이 고지를 전제로 설명하는 내부 운영 용도로 읽습니다. 식별자 없이, 서버가 고른 모습으로 전송되는 걸음은 규칙의 개인정보 정의 밖에 있다고 읽습니다. 이 읽기의 약점은 걸음이 행동이라는 점이며, 그 시간에 광장을 본 사람은 그것을 어떤 수집가와 연결할 수 있습니다. 시 단위로만 남긴 시각, 60초 상한, 7일이라는 기간이 그 가능성을 좁히지만 없애지는 못합니다. 요청에 실린 숨긴 ID는 그 요청에만 쓰이고 기록되지도 보관되지도 않습니다. 개인정보 고지의 서면 보존 정책은 § 312.10이 요구하는 대로 7일이라고 밝힙니다.19 이로써 서버는 아무것도 저장하지 않는 상태에서 걸음을 일주일 동안 저장하는 상태로 바뀌며, 이 읽기는 계획이 자리를 잡을 때까지 미뤄 둔 변호사가 살펴볼 몫입니다.23
  • 누구를 기록하는가. 13세 미만은 아무도 기록하지 않습니다. 10월 2일의 결정에 따라 그들에게는 광장이 아예 없습니다.23 isMultiplayerGamingRestricted가 true인 사람도 기록하지 않고(걸어 다닐 실시간 광장이 없습니다, 설정 1), isUnderage가 true인 사람(설정 2가 이미 아이 신호로 다룹니다)도, 신고된 연령대로 13~15세인 사람도 기록하지 않습니다. 마지막은 계획이 아직 쓰지 않은 것으로 남겨 둔 주니어 규칙의 첫 번째입니다.185223
  • 닮아서는 안 되는 것. 핏자국, 재생되는 죽음, 소환 표식, 플레이어 게시물로 덮인 벽. 메아리는 결코 생물이 아니며 결코 싸우지 않습니다.4144

검사. (a) 서버 테스트가 방문 10개를 기록하고, 8개가 보관되며 가장 오래된 것이 지워지는지, 그리고 시계를 7일 앞당기면 하나도 남지 않는지 확인합니다. (b) 스키마 테스트가 저장된 걸음에 경로, 시, 솔트, 태그 필드만 있고 핸들, ID, 모습 문자열이 없는지, 그리고 클라이언트가 받는 메아리에 모습, 경로, 시 필드만 있고 해시, 태그, 솔트, ID 필드가 없는지 확인합니다. 또한 메아리의 모습 문자열이 걸음 솔트의 키 해시가 고르는 넷 중 하나이며 걷는 사람이 무엇을 입었든 같은지, 그리고 평범한 넷 밖의 모습을 입은 사람의 경우 그것이 걷는 사람의 모습이 아닌지 확인합니다. (c) 실시간 상대가 없는 UI 테스트가 최대 8개의 메아리를 보여 주고, 어느 것도 탭에 반응하지 않는지 확인합니다. (d) 멀티플레이어 제한 플래그로 한 테스트, isUnderage로 한 테스트, 13~15세 구간으로 한 테스트가 서버가 그 클라이언트의 방문을 기록하지 않는지 확인합니다. (e) 서버 테스트가 두 수집가의 걸음을 저장하고, 그중 한 명을 숨긴 채 메아리를 요청해 그 수집가의 걸음이 빠졌는지 확인한 뒤, 숨긴 ID가 서버 상태 어디에도 없는지 확인합니다.

7.3 오늘의 인사

  • 방식. 마을 사람은 각자 오늘 플레이어와 이야기했는지 기억합니다.1110 현지 날짜로 그날 첫 대화에서는 인사 대사(“Back again!”(또 왔구나!) 같은 것으로, Kiradex를 위해 쓰고 server/app/lines.py의 인덱스 22 뒤에 추가합니다)를 고르고, 같은 날 그 뒤의 대화에서는 평범한 대사를 고릅니다.21 같은 마을 사람과 이야기한 날이 셋째 날을 맞을 때마다 그 마을 사람은 단골의 숨은 사다리를 한 칸 올라갑니다. 첫 대화부터 세며 결코 초기화되지 않고, 한 칸 오를 때마다 새 대사를 하나 말합니다. 게이지는 보여 주지 않습니다.
  • 감소 없음. 하루, 또는 한 달을 빠져도 잃는 것은 없습니다. Stardew의 우정은 이야기하지 않은 날마다 줄지만 이것은 결코 줄지 않습니다. PEGI가 “daily quests, log-in streak or event-based rewards that expire after a while”(일일 퀘스트, 로그인 연속 기록, 일정 시간이 지나면 만료되는 이벤트형 보상)을 PEGI 7로 등급을 매기고, “players are punished by losing rewards or game status when they do not return in time”(제때 돌아오지 않으면 보상이나 게임 내 지위를 잃는 식으로 플레이어가 벌을 받는) 경우를 PEGI 12로 매기기 때문입니다.105958
  • 어디에 두는가. 기기에 둡니다. 마을 사람 ID에서 마지막으로 이야기한 현지 날짜와 이야기한 날의 수로 가는 사전 하나를 수집가의 프로필에 두며, 그래서 서버는 플레이어별 저장소 없이 지낼 수 있습니다.

검사. (a) 단위 테스트가 한 마을 사람과 2026-10-05에 두 번, 2026-10-06에 한 번 이야기하고, 그날 첫 인사 두 번과 평범한 대사 한 번을 받는지 확인합니다. (b) 단위 테스트가 기기 날짜를 앞뒤로 옮기고 사다리의 칸이 한 번도 없어지지 않는지 확인합니다. (c) server/app를 검색해 이 기능을 위한 플레이어별 저장소가 추가되지 않았는지 확인합니다. (d) 몇 주 간격을 둔 사흘에 첫 대화를 한 단위 테스트가 그 마을 사람이 사다리를 한 칸 올라갔는지 확인합니다.

7.4 되돌려지지 않는 존재감

  • 전송. 지금처럼 완료된 타일마다 move를 하나 보냅니다. 현재 기준은 걷기에서 초당 4회, 달리기에서 6.0~6.4회입니다(60Hz로 모델링한 달리기와 명목 빈도의 달리기). 시리즈의 움직임 계약, 즉 움직임 브리프의 60Hz에서 걷기 타일당 16틱, 달리기 8틱으로는 3.75와 7.5이며, 이 브리프가 목표로 삼는 것이 그 계약입니다. 둘 다 초당 8회 제한 아래입니다.2122 서버는 중계하는 모든 moved에 자체 밀리초 스탬프를 붙입니다.
  • 그리기. 각 클라이언트는 받은 스탬프로부터 서버 시계의 추정값을 유지하고, 다른 수집가를 각각 그 추정값보다 350ms 늦은 렌더 시계로 그립니다. 렌더 시계를 사이에 두는 받은 스탬프 두 개 사이를 선형으로 그리고, 걷기 사이클은 걸은 거리로 진행합니다. 진행 중인 걸음은 결코 초기화하지 않습니다. 버퍼가 비면 마지막 타일에서 기다립니다. 격자 위를 걷는 사람은 딱 멈추므로 아무것도 외삽하지 않습니다. 점프는 걷는 사람이 6타일 넘게, 또는 2초 넘게 뒤처졌을 때만 합니다.716 350ms는 상한에서 나왔으며, 시계 추정이 정확하고 지터 말고는 단방향 지연이 없다고 가정한 모델에서 확인했습니다(3절). 움직임 계약의 걷기인 초당 3.75타일에서 한 걸음은 266.7ms이고, 한 걸음에 80ms 지터를 더하면 346.7ms이므로, 적어도 그만큼 긴 지연이라면 어떤 지터 추첨에서도 바닥나는 프레임이 없습니다. 그러면 시계 추정의 오차에 남는 여유는 약 3ms입니다. 추정이 그보다 더 서버 시계를 앞서 가면 프레임이 바닥날 수 있습니다. 325ms는 모델의 첫 지터 추첨에서 검사 (a)를 통과했고 80ms에서 바닥난 프레임은 6개였지만, 200번의 추첨 중 8번에서는 통과하지 못했습니다. 350ms는 어느 추첨에서도 바닥나지 않았습니다.7 이는 기기에서 캡처로 확인해야 할 출발값이지, 결과가 아닙니다.
  • 대기 상태. 입력 없이 120초가 지난 수집가를 대기 상태로 표시하고, 리그에 이미 있는 doze 대기 사이클로 보여 줍니다. Arcturus가 500ms 주기 240번에서 아바타를 대기 상태로 표시하는 것과 같습니다.1550 핑 없이 240초가 지나면 소켓을 닫습니다. 이 규칙은 두 출처를 제가 각색한 것입니다. 핑 확인은 Houdini의 것으로, 61초 창 동안 하트비트를 보내지 않은 클라이언트를 끊습니다. 240초는 Arcturus가 대기 중인 방 주인 아닌 사람을 내보내는 길이인데, Arcturus가 세는 것은 빠진 핑이 아니라 동작 없는 주기입니다.4815

검사. (a) 새 로직을 measure_remote_walk.py에 옮기되, 렌더 시계는 보내는 쪽 자신의 시계가 아니라 클라이언트의 서버 시계 추정값에서 가져오고, 보내는 쪽이 초당 3.75타일과 7.5타일일 때 여섯 경우 모두에서 뒤로 가는 프레임 0, 지터 0ms와 30ms에서 바닥나는 프레임 0, 80ms에서는 보내는 쪽이 전송하는 동안 그려지는 600프레임 중 바닥나는 프레임이 최대 10개일 것을 요구합니다. 정확한 시계를 쓴 모델은 350ms에서 200번의 지터 추첨 각각에 대해 여섯 경우 모두 바닥나는 프레임이 0입니다.7 (b) 대본대로 움직이는 상대가 광장을 오른쪽으로 가로질러 걷는 UI 테스트에서, 상대의 x가 줄어드는 프레임이 하나도 캡처되지 않는지 확인합니다. (c) 서버 테스트가 중계된 모든 moved에 스탬프가 있는지 확인합니다. (d) 서버 테스트가 핑 없이 시계를 241초 앞당기고 소켓이 닫혔는지 확인합니다.

7.5 대사집

  • 모양. 여섯 갈래, 출시 때 갈래마다 대사 8~12개, 모두 48~72개이며, 모든 대사는 두 번의 탭(갈래, 그다음 대사)으로 닿습니다. 인사, 대답(좋아하는 시대, 수집한 기간, 어느 세트), 수집, 교환(교환을 주선하되 값을 매기지는 않음), 응원, 그리고 작별입니다. 지금은 플레이어 대사 14개가 목록 하나에 있습니다.21 이모트는 별도의 줄로 남습니다. Game Center가 소통 제한을 보고하거나 미성년자인 플레이어에게는 대사집을 아예 주지 않습니다(7.6, 설정 2).
  • 늘어남. 플레이로 최대 30개의 대사가 더 풀리며, 하루에 많아야 하나씩, 7.3절의 단골 사다리와 레벨에서 나옵니다.1235 목록은 서버가 가지고 있고, 이미 도착할 때 목록을 보내므로 한 시간 안에 대사를 내릴 수 있습니다.20
  • 원전과 비교. 간이 회화는 940개 단어를 열어 2~10칸 격자로 조합합니다. 모여봐요 동물의 숲의 리액션은 88개입니다. Emerald의 유니언 룸은 15자 키보드 입력을 받았습니다.38124 Kiradex의 대사는 완전한 문장이며 결코 단어로 조합되지 않으므로, 어떤 선택을 이어 붙여도 이름, 숫자, 장소를 철자로 만들 수 없습니다.
  • 닮아서는 안 되는 것. 간이 회화의 그룹 구조나 단어 격자, 유행어 목록, 또는 프랜차이즈 단어.40

제안하는 대사집의 도식. 뿌리 노드에서 인사, 대답, 수집, 교환, 응원, 작별의 여섯 갈래가 나오고, 갈래마다 채워진 대사 칸 8개와 빈 칸 4개가 있어 갈래당 8~12개 대사. 아래 메모: 현재는 목록 하나에 플레이어 대사 14개. Emerald의 간이 회화는 2~10칸 격자에 열린 단어 940개. Emerald의 유니언 룸은 15자 키보드 입력 메시지와 등록 문구 10개.

제안하는 대사집. 이 글에서 측정이 아니라 제안을 담은 유일한 그림입니다.26

검사. (a) lines.py에 대한 테스트가 출시 때 여섯 갈래에 각각 대사 8~12개가 있는지, 그리고 모든 대사가 뿌리에서 두 번의 탭 안에 있는지 확인합니다. (b) 테스트가 숫자나 자유 입력 칸이 들어간 대사가 없는지 확인합니다. (c) 테스트가 pret의 간이 회화 단어 목록을 읽어, 항목과 같은 대사가 없고 두 단어 이상인 항목이 대사 안에 들어 있지 않은지 확인합니다. (d) 단위 테스트가 이틀 연속으로 하나씩 대사를 풀고, 같은 날의 두 번째 해금은 거부하는지 확인합니다.

7.6 안전 설정, 각각 검사와 함께

  1. Game Center의 제한은 실시간이든 재생이든 다른 수집가를 끕니다. GKLocalPlayer.local.isMultiplayerGamingRestricted가 true이면(친구만 포함), 광장은 조용한 광장으로 열립니다. 마을 사람만 있고, 메아리도 WebSocket도 없습니다. 마을 사람과 방의 시계는 계정 ID, 기기 ID, 숨긴 ID를 담지 않은 요청 하나로 옵니다. 서버는 요청의 IP 주소를 그 요청에 답하는 데만 쓰고, 광장 서버는 그것을 기록하지 않습니다. 호스트 자체의 엣지 요청 로그는 여기서 검증하지 않았습니다(설정 6).18 읽기: 페이지는 “If your game uses a custom multiplayer feature, you should disable it”(게임이 독자적인 멀티플레이어 기능을 쓴다면 그것을 비활성화해야 한다)라고 말하고, 메아리는 진짜 수집가의 걸음을 재생한 것, 2절에서 살펴본 종류의 다른 플레이어의 흔적이며, 보는 사람이 알아볼 수도 있는 것입니다(7.2). Wikipedia는 Dark Souls의 다른 플레이어의 유령 같은 모습을 게임의 Multiplayer 제목 아래에서 설명하지만, 그것이 녹화인지 그 시점에 접속한 플레이어인지는 말하지 않습니다. 이 출처는 엇갈립니다. 그 제목 아래 본문은 게임이 “integrates online features into the single-player world”(온라인 기능을 싱글플레이어 세계에 통합한다)라고 하고, 유령과는 별도로 협력 소환과 침입을 “Direct multiplayer”(직접 멀티플레이어)로 제시하기 때문입니다. 그래도 브리프는 보수적인 쪽을 택합니다. 제가 읽기로는 메아리는 광장의 독자적인 멀티플레이어 기능의 일부이며, 보호자가 멀티플레이어를 제한한 플레이어는 아무것도 보지 않습니다.1841 이 플레이어에게 메아리를 되돌릴 수 있는 것은 App Review나 Apple이 서면으로, 식별자 없이 평범한 모습으로 재생되는 걸음을 멀티플레이어가 아닌 것으로 읽어 주는 경우입니다. 이 브리프는 그것을 가정하지 않습니다. 검사: 플래그를 true로 스텁한 UI 테스트가 WebSocket이 열리지 않는지, 메아리가 그려지지 않는지, 광장이 조용하다고 알리는지 확인합니다. 서버 테스트가 조용한 광장의 경로를 호출해 응답에 메아리가 없고 서버 로그에 IP 주소가 없는지 확인합니다.
  2. 독자적인 소통 끄기. isPersonalizedCommunicationRestricted나 isUnderage가 true이면 기본값은 이모트만입니다. 대사집은 숨겨지고, say는 보내지지 않으며, 다른 수집가의 대사는 이 플레이어에게 그려지지 않고, 음성, 초대 메시지, 입력란은 어떤 경우에도 없습니다.5152 읽기: 페이지는 “If your game includes any custom communication features, you should disable them”(게임에 독자적인 소통 기능이 있다면 그것을 비활성화해야 한다)라고 말하며, 정해진 문장의 대사집은 독자적인 소통 기능입니다. Game Center가 자체 기능에서 아이들에게 정해진 메시지를 허용한다고 해서 게임 자체의 기능이 면제되지는 않습니다.5117 이모트를 남기는 것은 손 흔들기나 끄덕임에는 아이가 누군가에게 닿는 데 쓸 만한 것이 아무것도 담기지 않기 때문입니다. 하지만 이모트도 일종의 소통이며(Wikipedia의 Journey 설명은 그 차임을 “The only form of communication between the two”(두 사람 사이의 유일한 소통 방식)라고 부릅니다), 이 읽기에서 가장 약한 고리입니다. App Review가 이모트도 독자적인 소통 기능으로 읽는다면 이모트도 빠지고, 이 플레이어에게 광장은 존재감뿐이 됩니다.5 이 플레이어에게 대사를 남기는 것은 Apple의 서면 동의가 필요한 예외이며, 이 브리프는 그것을 택하지 않습니다. 검사: Kiradex/World에서 TextField, TextEditor, UITextField를 검색해 아무것도 나오지 않는지, 그리고 각 플래그를 true로 스텁한 UI 테스트가 이모트 줄을 보여 주고, 대사집은 보여 주지 않으며, say를 보내지 않고, 다른 수집가의 대사를 그리지 않는지 확인합니다.
  3. 10월 2일 결정대로 Declared Age Range에 따른 연령대. 13세 미만: 광장 없음, 자동 생성 핸들만. 13~15세: 주니어 규칙이 적용된 광장이며, 그 첫 규칙은 걸음을 결코 기록하지 않는 것. 16세 이상 또는 신고하지 않음: 완전한 광장.2354 검사: 세 연령대와 세 가지 Game Center 플래그에 걸친 모드 표의 단위 테스트.
  4. 신고와 숨기기가 메아리에도 닿습니다. 수집가를 숨기면 실시간 모습이 숨겨지고, 7.2절처럼 요청마다 서버에서 걸러 메아리도 숨겨집니다. 신고는 모든 수집가의 패널에 남습니다.55 메아리는 평범한 모습을 하고 패널이 없으므로, 플레이어가 숨긴 수집가의 메아리를 골라 숨길 수는 없습니다. 그 일은 서버의 필터가 합니다. 숨김은 그 전후에 저장된 걸음 모두에 닿지만, 다른 플레이어가 실시간으로 본 것이나 걸음에서 알아본 것에는 닿지 못합니다. 검사: 7.2(e)의 서버 테스트, 그리고 대본대로 움직이는 상대를 숨기고 그 사람의 기록된 걸음을 재생해 그것이 그려지지 않는지 확인하는 UI 테스트.
  5. 무작위 짝짓기 없음, 익명 채팅 없음. 낯선 사람끼리는 광장을 함께 쓸 뿐이며, 낯선 두 사람을 짝지어 사적인 공간에 넣는 것은 없습니다.55 검사: server/app/main.py의 핸들러에 대한 테스트가 Game Center 친구가 아닌 두 수집가 사이에 채널을 여는 메시지가 없는지 확인합니다.
  6. 서면 보존. 개인정보 고지에 서면 데이터 보존 정책을 더합니다. 존재감은 메모리에만. 메아리는 7일이며, 각각 걷는 사람 ID의 키 해시를 가지는데, 그것은 서버에 머물고 숨김 적용에만 쓰이며 결코 전송되지 않습니다. 요청에 실린 숨긴 ID는 보관하지 않습니다. 연결의 IP 주소는 그 연결에 답하고 회신을 보내는 데만 쓰며, 저는 이를 § 312.5(c)(7)에 따른 내부 운영 지원으로 읽고, 광장 서버는 그것을 기록하지 않습니다. 신고는 사유와 방으로 기록합니다.19 현재 서버는 uvicorn 0.37.0을 기본값 그대로 시작하며(server/Procfile, --proxy-headers), uvicorn 자체의 코드를 읽어 보면 그 기본값은 모든 HTTP 요청(접근 로그는 끄지 않는 한 켜져 있음)과 수락된 모든 WebSocket(uvicorn.error의 INFO 줄)에 대해 클라이언트의 전달된 주소를 로그에 씁니다. 브리프는 이를 --no-access-log --log-level warning으로 시작합니다.57 검사: 고지의 URL에 설정에서 닿을 수 있는지, 7.2(a)의 서버 테스트가 7일을 지키는지, 그리고 운영용 시작 명령으로 WebSocket을 열고 조용한 광장의 경로를 호출하는 테스트가 수집된 로그에서 IP 주소를 찾지 못하는지 확인합니다. 호스트의 엣지 요청 로그는 이 테스트 밖에 있으며 여기서 검증하지 않았습니다. 그것이 클라이언트의 주소를 기록하는지, 기록한다면 얼마나 오래인지는 고지를 쓰기 전에 호스트에 확인해야 합니다.
  7. 월드에 서드파티 분석이나 광고를 두지 않습니다.55 검사: 앱 타깃의 의존성 감사에서 분석이나 광고 SDK가 하나도 나오지 않는지 확인합니다.

거부할 것

  • 자체 플레이 타이머. Screen Time이 이미 앱 사용 시간을 제한하고, Club Penguin의 서버는 아이에게 허용된 분을 거꾸로 세어 0에서 연결을 끊었습니다. 보호자가 이미 아는 것은 플랫폼의 제한입니다.1748
  • 팔로워 수, 그리고 누가 누구를 좋아하는지를 세는 모든 숫자. 계획이 이미 배제하고 있습니다.23
  • 이름이 붙은 방문자 목록, 즉 누가 다녀갔는지 보여 주는 페이지. 메아리는 일부러 이름이 없습니다. ID도, 걷는 사람의 모습에서 가져온 것도 없습니다. 그래도 익명은 아닙니다. 그 시간에 광장에 있었던 사람이나 친구의 습관을 아는 사람은 걸음을 알아볼 수 있으며, 그래서 메아리는 시까지만 담고 그보다 세밀한 시각은 담지 않으며, 메아리를 읽어 낼 목록도 없습니다.
  • 탭할 수 있는 메아리, 말을 걸거나 따라갈 수 있는 메아리.
  • 오늘의 인사 게이지, 또는 돌아오지 않았다고 잃는 것.59

이 브리프에 넣지 않은 것

  • 사람들이 꾸미는 방. 가구, 모자, 다른 수집가의 방 방문은 이 시리즈의 다음 글에서 다룹니다.
  • 새 대사의 문구. 브리프는 대사집의 모양을 정하고, 문장은 Kiradex를 위해 쓰고 따로 현지화합니다.
  • 함께하는 놀이. 계획이 아직 만들지 않은 것으로 꼽은 교환, 자랑하기, 교환 화면은 이 브리프의 범위 밖입니다.23
  • 측정한 게임에서 가져온 것. Pokémon, Stardew Valley, 동물의 숲, Dark Souls, Journey, Death Stranding, Splatoon, Habbo, Club Penguin의 생물, 지명, 마크, 타일, 소리는 하나도 쓰지 않습니다.
  • 기기에서의 타이밍. 검사는 동작, 캡처, 모델이며, 여기서는 어떤 프레임 시간도 약속하지 않습니다.

핵심 요약

아트를 그리는 분께

  • 걷는 사람보다 서 있는 사람을 더 많이 그리십시오. Emerald의 맵에 늘 있는 마을 사람 열에 여섯은 자리에 서 있으며, 대부분은 한 방향을 보고 일부는 주위를 둘러봅니다(스토리 배우까지 세면 열에 일곱). 서 있는 인물마다 방향과 둘러보기를 주고, 걷기 사이클은 돌아다니는 열에 넷 가까운 사람들을 위해 아껴 두십시오.1
  • 다른 플레이어 걸음의 메아리는 사람이 아니라 흔적으로 읽히게 하십시오. 흐릿하고, 이름 없고, 아무것도 들지 않고, 문에서 사라지는 모습으로. 그 그림의 어디에도 탭을 부르는 것이 있어서는 안 됩니다.5

엔진을 만드는 분께

  • 마을의 하루는 도착할 때 경과 일수를 받는 실행 한 번으로 정산하고, 마을 사람의 타일은 각 휴대폰의 시계가 아니라 방 안의 모두가 함께 쓰는 하나의 시계로 계산하십시오. 방이 비면 반환하는 서버 루프는 Kiradex에서 측정한 것처럼 마을을 얼려 버립니다.36
  • 서버의 걸음 간격을 정수 틱으로 만들고, 각 걸음을 주어진 간격에 걸쳐 그리십시오. 0.5초 틱 위의 0.7초 걸음은 1.0초마다 내딛고 0.25초에 그려져, 시간의 75퍼센트를 가만히 서 있는 걸음이 됩니다.6
  • 타일마다 업데이트를 하나 보내고, 서버에서 스탬프를 찍고, 다른 걷는 사람들은 추정 서버 시각보다 350ms 늦은 시계로, 진행 중인 걸음을 결코 초기화하지 않고 그리십시오. 모델에서 시리즈의 초당 3.75타일과 7.5타일, 정확한 시계 추정을 가정하면, 지터 80ms까지 뒤로 가는 프레임과 바닥나는 프레임이 0입니다.7
  • 소켓을 열기 전에 isMultiplayerGamingRestricted, isPersonalizedCommunicationRestricted, isUnderage를 읽으십시오. 친구만은 제한된 것으로 읽히며, 제가 읽기로는 제한된 플레이어에게는 다른 플레이어의 재생도 보여 주지 않아야 합니다.185152

게임 루프를 설계하는 분께

  • 다른 사람의 흔적은 썩어 가는 몇 개의 고정 슬롯에 두십시오. 7일 동안 걸음 여덟 개면 누군가 여기 있었다고 말하기에 충분합니다. 숨김은 서버에서 적용하고, 흔적은 ID 없이 걷는 사람 자신의 모습이 아닌 평범한 모습으로 보내며, 걸음은 그것을 본 사람이 여전히 알아볼 수 있다는 것을 분명히 말하십시오.414
  • 오늘의 인사는 플래그 하나로 기억하고, 빠진 날 때문에 무언가를 빼앗지 마십시오. PEGI는 만료되는 연속 기록에 등급을 매깁니다.59
  • 낯선 사람들에게는 단어가 아니라 문장을 주십시오. 완전한 문장의 대사집으로는 무엇도 철자로 만들어 낼 수 없고, Game Center는 이미 아이들을 정해진 메시지로 제한합니다. Game Center가 소통이 제한되었다고 하면 대사집도 거두십시오.381751

자주 묻는 질문

다른 플레이어가 아무도 온라인이 아닐 때 게임 속 마을을 살아 있게 느끼게 하려면 어떻게 합니까?

자기 일과를 가진 마을 사람들과, 그곳에 있었던 사람들의 흔적을 주십시오. Pokémon Emerald의 16개 마을과 도시에서 숨김 플래그가 없는 마을 사람 100명 중 59명은 자리에 서 있고, 39명은 한두 셀 범위를 돌아다니며, 2명은 제자리걸음을 합니다(플래그로 사라질 수 있는, 대부분 서 있는 스토리 배우까지 센 158명 전체로는 71.5%, 27.2%, 1.3%). 이들은 플레이어의 속도로 걷고 걸음 사이에 0.54~2.14초를 기다립니다. Stardew Valley의 주민들은 22개의 키 형식으로 고른, 시각이 붙은 지점 몇 개로 된 하루를 보냅니다. 그리고 Emerald의 기록 교환은 다른 플레이어의 기지, 유행 문구, 주민을 내 마을로 복사합니다.124

탑다운 픽셀 게임에서 NPC는 얼마나 자주 움직여야 합니까?

Emerald에서 돌아다니는 사람은 16프레임짜리 한 걸음(268ms, 플레이어의 걷기와 같은 동작)을 내딛고 32, 64, 96, 128프레임을 기다리므로, 움직이는 시간은 내딛은 걸음 하나 기준으로 많아야 11~33퍼센트이고, 고른 걸음이 막히면 그보다 적습니다. 마을 사람 대부분은 결코 걸음을 내딛지 않는 기본 이동 유형을 가지지만, 스토리 스크립트는 그런 사람도 걷게 할 수 있습니다. 놀러오세요 동물의 숲은 반대로 가서 주민을 플레이어보다 느리게 만들었습니다. 나쁘게 읽히는 것은 어느 쪽도 아닌 속도입니다. Kiradex의 수집가들은 플레이어의 속도로 한 타일씩 움직이고 타일 사이에서 0.75초 멈춥니다.1326

Pokémon Emerald의 기록 교환이란 무엇입니까?

통신 케이블로 주고받는 것으로, 각 게임이 상대에게 11가지 기록으로 된 5,188바이트의 고정 구조체를 보냅니다. 그 안에는 비밀기지 20개, 텔레비전 슬롯 25개, 뉴스 16개, 유행 문구 5개가 있습니다. 상대의 보라시티 할아버지가 내 할아버지를 대신하며, 유행을 좇는 사람의 도착만이 그가 유행어를 하나 더 가르치게 해 줍니다.435

2D 멀티플레이어 게임에는 보간 지연이 얼마나 필요합니까?

Glenn Fiedler의 규칙은 잃어버린 패킷 두 개를 견딜 만큼, 즉 전송 간격의 약 세 배에 지터를 위한 한두 프레임을 더한 것입니다.16 TCP 위에서 돌아가는 WebSocket에서는 아무것도 잃지 않고 늦은 패킷은 재전송되므로, 제가 읽기로는 예산이 잃어버린 패킷이 아니라 지터이며, 한 간격을 조금 넘는 버퍼로 충분할 수 있습니다. 타일마다 업데이트를 하나 보내는 격자 세계에서, 시리즈의 걷기 초당 3.75타일과 달리기 7.5타일로 Kiradex의 걷기를 모델링했더니, 추정 서버 시각보다 350ms 늦은 렌더 시계가 지터 80ms까지 뒤로 가는 프레임 0, 바닥나는 프레임 0을 보였습니다. 한 번의 지터 추첨에서, 80ms 걷기 기준 300ms는 600프레임 중 27개, 325ms는 6개가 바닥났고, 100ms는 모든 경우에서 125~456개가 바닥났으며, 200번의 추첨에서 350ms는 한 번도 바닥나지 않았습니다. 350ms는 상한에 기댑니다. 초당 3.75타일에서 266.7ms인 한 걸음에 80ms 지터를 더하면 346.7ms입니다. 이것은 곧은 길, 균등한 지터, 정확한 시계 추정, 기본 지연 없음을 가정한 모델이지, 기기 캡처가 아닙니다.7

iOS 게임에서 아이들이 채팅을 쓸 수 있습니까?

Game Center는 아이들이 “cannot send or receive user-inputted text”(사용자가 입력한 텍스트를 보내거나 받을 수 없다)이고 “restricted to sending and receiving preset messages”(정해진 메시지를 보내고 받는 것으로 제한된다)라고 하며, 음성 채팅은 비활성화됩니다. Apple의 문서는 isPersonalizedCommunicationRestricted나 isUnderage가 true일 때 “any custom communication features”(모든 독자적인 소통 기능)를 가진 게임에게 그것을 비활성화하라고 하며, 가이드라인 1.2는 사용자 생성 콘텐츠에 대해 필터링, 신고, 차단, 공개된 연락처를 요구합니다. 그 문장은 정해진 메시지를 면제하지 않으므로, 제가 읽기로는 Game Center가 자체 기능에서 아이들에게 정해진 메시지를 허용하더라도 어느 한 플래그가 true이면 게임 자체의 정해진 대사는 빠집니다. Kiradex의 브리프는 그 플레이어에게 이모트만 남기며, 그 읽기는 Apple의 문구가 아니라 제 것입니다.175155

COPPA는 다른 플레이어의 움직임을 저장해 유령으로 재생하는 것을 허용합니까?

저는 변호사가 아니며, 이것은 읽기이지 자문이 아닙니다. 개정된 규칙은 “A persistent identifier that can be used to recognize a user over time”(시간이 지나도 사용자를 알아보는 데 쓰일 수 있는 영구 식별자)을 개인정보로 세고, 운영자가 고지를 전제로 영구 식별자만을 “for the sole purpose of providing support for the internal operations”(내부 운영을 지원하는 목적으로만) 쓰는 것을 허용하며, 2026년 4월 22일부터는 삭제 기한을 담은 서면 보존 정책을 요구합니다. Kiradex의 설계는 핸들, 계정 ID, 기기 ID, 걷는 사람의 모습이 없는 걸음을 저장하고, 여기에 걷는 사람 ID의 키 해시를 더하는데, 이 해시는 서버에 머물고 다른 플레이어의 숨김을 적용하는 데만 쓰이며 결코 전송되지 않습니다. 메아리는 서버가 고르는 평범한 모습 넷 중 하나로 그려지고, 둘 다 7일 동안 보관되며 그 정책에 적힙니다. 저는 해시를 내부 운영에 쓰이는 영구 식별자로, 전송되는 걸음을 정의 밖의 것으로 다루지만, 걸음은 그 시간에 광장을 본 사람이 어떤 수집가와 연결할 수도 있는 행동입니다. 그래서 메아리는 익명이 아니라 이름이 없을 뿐이며, 최종 설계는 변호사가 확인해야 합니다.19

이 사이트의 관련 글: iPhone에서의 픽셀 아트 월드는 이 시리즈의 첫 번째 가이드로, 이 글의 마을이 자리한 광장과 타일 시스템을 다루며, 그 법률 FAQ가 브리프가 따르는 특허의 선을 긋습니다. 픽셀 아트 인물은 수집가들과, 메아리가 입는 평범한 모습을 만드는 포지를 지었고, 오늘의 인사가 줄어들지 않게 하는 PEGI 규칙을 인용합니다. 픽셀 아트 건물은 마을 사람들이 밤에 들어가는 문을 지었습니다. 픽셀 아트의 움직임은 휴대용 게임기의 걷기를 측정하고, 앱 자체의 걷기를 모델링하며, 브리프가 다른 수집가에게 적용하는 “걸음을 결코 초기화하지 않는다” 규칙을 정합니다. 그리고 개발자를 위한 iPhone Duo는 광장이 그려지는 기기를 다룹니다.

출처


  1. 저자의 측정, measure_npcs.py(이 글을 위한 저자의 증거 폴더에 있음), 2026년 10월 5일 수정 후 재실행, 출력은 measure_npcs.out으로 저장. 입력: pret의 pokeemerald 디컴파일, 커밋 731ad5b(master, 2026년 10월 1일)의 얕은 클론. include/constants/event_object_movement.h(81가지 이동 유형), 16개 마을과 도시의 data/maps/*/map.json 파일(오브젝트 이벤트, 그래픽 ID, 이동 유형, 범위, 숨김 플래그), src/event_object_movement.c(걸음 테이블, 59.7275Hz로 환산한 세 대기 테이블, 어느 이동 유형이 어느 대기를 설정하는지, 그리고 MovementType_WanderAround_Step4). 수정판은 오브젝트 이벤트를 graphics_id로 사람, 오브젝트(ITEM_BALL 6, TRUCK 2, MR_BRINEYS_BOAT 1), 생물(5)로 분류하며, 분류하지 않은 그래픽 ID가 나오면 멈춤. 스프라이트가 변수에서 오는 VAR_0과 VAR_3 이벤트는 모두 라이벌의 숨김 플래그를 가지므로 사람으로 셈. 이전 판은 오브젝트 이벤트 172개를 모두 마을 사람으로 셌음. 그것과 그 출력은 .r1 접미사를 붙여 옆에 남겨 두었고, 수정판의 출력은 이전 줄들을 바꾸지 않고 되풀이함. 첫 수정판과 그 출력은 .r2 접미사로 남겨 둠. 현재 스크립트는 두 번째 수정판으로, 158명을 오브젝트 이벤트의 숨김 플래그로 나눔. 플래그가 있는 58명(54명이 서 있고 4명이 돌아다님. 스크립트 안의 고정된 그래픽 ID 목록에 따르면 그중 41명이 악의 조직, 라이벌, 이름 있는 인물의 스프라이트를 씀)과 없는 100명(59명이 서 있고, 39명이 돌아다니며, 2명이 제자리걸음)이며, 세 마을의 숨김 플래그 없는 사람을 다시 셈(미로마을 2, 등화시티 3, 보라시티 6). 그 출력은 이전의 모든 줄을 바꾸지 않고 되풀이함. 숨김 플래그가 없는 사람은 맵을 불러올 때마다 있음. 58명을 스토리 배우라고 부르는 것은 그 스프라이트와 플래그 이름에 대한 저의 읽기이지, 데이터의 필드가 아님. 움직이는 비율(11~33퍼센트)은 걸음과 대기 줄에 대한 산수로, 16프레임을 16에 32~128을 더한 값으로 나눈, 내딛은 걸음 하나 기준의 값임. 고른 방향이 막힌 돌아다니는 사람은 움직이지 않고 다시 방향을 돌려 기다리므로 이것은 상한임. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. 저자의 측정, measure_daily.py(같은 폴더), 2026년 10월 5일 재실행, 출력은 measure_daily.out과 동일. 입력: pret pokeemerald 731ad5b의 include/constants/flags.h와 src/clock.c. 저장한 Stardew Valley Wiki 페이지 “Villagers”(oldid 191346, 2026년 2월 26일 편집), “Pierre”, “Modding:Schedule data”(22개 키 형식은 페이지의 표에서 셈). Nookipedia의 “Reaction” 페이지(모여봐요 동물의 숲의 버전별 개수). ↩↩↩↩↩↩↩↩↩↩↩

  3. pret, pokeemerald/src/clock.c(DoTimeBasedEvents, 그리고 마지막으로 실행된 뒤의 날 수인 daysSince를 받아 한 번 실행되는 UpdatePerDay), 커밋 731ad5b. DoTimeBasedEvents는 src/overworld.c에서 맵을 불러올 때와 src/field_tasks.c의 주기적인 필드 작업에서도 호출됨. 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/clock.c ↩↩↩↩↩↩↩

  4. 저자의 측정, measure_record_mixing.py(같은 폴더), 2026년 10월 5일 재실행, 출력은 measure_record_mixing.out과 동일. 입력: pret pokeemerald 731ad5b의 src/record_mixing.c(struct PlayerRecordEmerald, 그 11개 멤버와 0x1444바이트 크기), include/constants/global.h와 관련 헤더(SECRET_BASES_COUNT, TV_SHOWS_COUNT, POKE_NEWS_COUNT, SAVED_TRENDS_COUNT, APPRENTICE_COUNT), src/mauville_old_man.c(다섯 할아버지와 선택 규칙), 그리고 src/union_room_chat.c, include/union_room.h, include/constants/global.h(유니언 룸의 리더, 그룹 인원, 스프라이트, 활동 코드, 키보드 페이지, MAX_MESSAGE_LENGTH, UNION_ROOM_KB_ROW_COUNT). ↩↩↩↩↩↩↩↩↩↩↩↩

  5. Wikipedia, “Journey (2012 video game)”, Gameplay와 Development 절(각 서술은 자체 출처를 인용), 2026년 10월 4일 저장, https://en.wikipedia.org/wiki/Journey_(2012_video_game). thatgamecompany 자체의 Journey 페이지도 저장했지만 내비게이션만 담겨 있음. ↩↩↩↩↩↩↩

  6. 저자의 측정, measure_npc_cadence.py(같은 폴더), 2026년 10월 5일 수정 후 재실행, 출력은 measure_npc_cadence.out으로 저장. 수정은 평균 30.7초를 낸 한 수집가의 연속된 대사 사이 간격 465개 옆에 모든 said 메시지의 개수(한 시간에 469)를 세는 것만 더했고, 다른 줄은 바뀌지 않음(이전 판은 .pre-astra 접미사로 남겨 둠). 2026년 10월 5일에 읽은 작업 트리(파일과 해시는 주석 20)에서 Kiradex 서버 자체의 app.npcs(STEP_SECONDS, make_all)와 app.rooms(Plaza)를 가져오고, server/app/main.py에서 TICK_SECONDS를 읽어, 시드 1의 네 수집가를 이상적인 0.5초 시계로 시뮬레이션상 3,600초 동안 진행하며 모든 moved와 said 메시지를 기록함. 연속된 걸음 사이가 1.5초 이하인 간격은 구간 안으로 세고, 그보다 긴 틈은 멈춤으로 봄. 빈 방 테스트는 Room.tick에 조기 반환이 있는지 확인하고, 새로 만든 수집가에게 advance(10000.0)을 한 번 호출함. 25퍼센트와 75퍼센트는 클라이언트의 250ms 걸음(PlazaRig.tilesPerSecond = 4)을 측정한 1.0초 간격으로 나눈 값. 이 실행은 measure_kiradex.py가 상수만으로 도출한 0.7초와 36퍼센트를 대체함. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  7. 저자의 모델, measure_remote_walk.py(같은 폴더), 2026년 10월 5일 수정 후 재실행, 출력은 measure_remote_walk.out으로 저장. 리그의 산술을 64비트 부동소수점으로 했던 이전 판과 그 출력은 .pre-f32 접미사로 남겨 둠. 수정판은 리그가 Float로 하는 모든 연산을 32비트(NumPy float32)로, 코드의 순서대로 수행함. progress는 Float(PlazaRig.swift 29번째 줄), update는 Float(event.deltaTime)을 받고(PlazaStage.swift 149번째 줄), advance()는 dt * tilesPerSecond * (running ? runPace : 1) / length를 더함(660번째 줄). running은 플레이어에게만 설정됨(614번째 줄). 위치는 (Float(x) + 0.5) * 16(TileMap.swift 235번째 줄)이고 from + (to - from) * progress에 그려지며, 정수 픽셀로 반올림됨(0에서 먼 쪽으로, 670번째와 726번째 줄). 1/60초 프레임 15개를 더하면 32비트에서는 정확히 1.0, 64비트에서는 0.9999999999999999가 된다는 확인을 출력함. 10월 5일에 읽은 PlazaRig.move(other:to:facing:)를 다시 구현하고(걷는 사람의 마지막 정수 타일에서의 경로, 메시지마다 progress = 0, 경로가 비었거나 6걸음보다 길면 점프), 걸음이 남아 있고 progress가 0.05를 넘을 때의 초기화를 되돌림으로, 그려진 픽셀 위치가 이전 프레임보다 작은 것을 뒤로 가는 프레임으로 세며, 뒤처짐은 그려진 픽셀 위치로 구함. 이와 별도로 일정한 렌더 시계 rt = now - delay(지연 100, 250, 300, 325, 350ms) 위의 스냅샷 보간을 모델링함. now는 보내는 쪽의 시계이므로, 모델링한 규칙은 최신 업데이트에서 지연을 뺀 것이 아니라 추정 서버 시각에서 지연을 뺀 것임. 이 절반은 리그가 아니라 제안하는 렌더러이며 64비트 부동소수점으로 돌아감. 가정: 모든 프레임의 deltaTime은 정확히 1/60초. 융합 곱셈-덧셈 없음. 보내는 쪽과 렌더러 사이의 완벽한 시계 동기화. 기본 단방향 지연이 없어 업데이트는 스탬프에 지터를 더한 시각에 도착. 곧고 탁 트인 길(경로 길이는 맨해튼 거리. 리그에서 √2배 걸리는 대각선 걸음은 모델링하지 않음). 보내는 쪽은 완료된 타일마다 이동 하나를 보내며, 두 절반 모두 현재의 걷기 초당 4타일과 달리기의 명목 6.4로, 버퍼에서는 추가로 시리즈의 움직임 계약인 초당 3.75타일과 7.5타일로도 보냄. J를 0, 30, 80으로 한 0~J ms의 균등한 지터. 초당 60프레임. 시드 1. 리그는 경우마다 시뮬레이션상 12초(전송 10초와 안정화 2초), 버퍼를 쓴 각 실행은 660프레임(11초)을 돌리며, 바닥나는 프레임은 보내는 쪽이 아직 전송하는 동안 그려지는 600프레임에 대해서만 셈. 두 번째 스크립트 measure_remote_walk_seeds.py(같은 폴더)는 2026년 10월 5일에 실행했고 출력은 measure_remote_walk_seeds.out으로 저장. measure_remote_walk.py의 소스에서 같은 보간 함수를 불러오고, 시드 1이 바닥나는 프레임 27, 6, 0을 재현하는지 확인한 뒤, 초당 3.75타일과 7.5타일의 버퍼를 시드 1~200으로 다시 돌림. 지터 80ms 걷기에서 325ms는 200개 시드 모두에서 프레임이 바닥났고 8개에서 10을 넘었으며, 350ms는 어디서도 없었고, 325ms와 350ms의 다른 모든 경우도 없었음. 350ms의 근거가 되는 상한도 출력함. 초당 3.75타일의 한 걸음은 266.67ms, 80ms 지터를 더하면 346.67ms로 3.33ms가 남음. 또한 렌더 시계를 어긋나게 해 서버 시계보다 앞서 가는 시계 추정도 모델링함. 프레임과 걸음이 1/60초 격자에 맞춰지는 이 모델에서는 10ms 앞서도 200개 시드에서 바닥나는 프레임이 없었고, 25ms 앞서면 325ms처럼 동작함. 따라서 본문의 3ms는 상한이 보장하는 값이지, 이 모델이 바닥나기 시작하는 지점이 아님. 이것은 모델이지, 두 기기의 캡처가 아님. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  8. pret, pokeemerald/data/maps/, 16개 마을과 도시의 map.json 파일(그중 LittlerootTown, PetalburgCity, MauvilleCity), 그리고 data/maps/PetalburgCity/scripts.inc(체육관 소년 LOCALID_GYM_BOY. map.json에서는 MOVEMENT_TYPE_LOOK_AROUND이며 숨김 플래그 없음. PetalburgCity_Movement_BoyWalkToGym 이동으로 체육관까지 걸어감), 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/tree/master/data/maps ↩↩↩↩

  9. Stardew Valley Wiki, “Modding:Schedule data”, 2026년 10월 4일 저장, https://stardewvalleywiki.com/Modding:Schedule_data. 일정 형식, 게임 버전 1.5.1 기준 Abigail의 원본 데이터, 키 형식과 그 순서, 그리고 “Limitations” 메모. ↩↩↩↩↩↩↩↩

  10. Stardew Valley Wiki, “Friendship”, 2026년 10월 4일 저장, https://stardewvalleywiki.com/Friendship. 하트당 포인트, 우정이 오르고 내리는 방식, 감소 표, 선물, 생일 배율, 하트 이벤트. ↩↩↩↩↩↩

  11. Nookipedia, “Friendship”, 모여봐요 동물의 숲 절, 2026년 10월 4일 저장, https://nookipedia.com/wiki/Friendship. ↩↩↩↩

  12. Nookipedia, “Reaction”, 모여봐요 동물의 숲 절, 2026년 10월 4일 저장, https://nookipedia.com/wiki/Reaction. 버전별 개수는 measure_daily.py에서 나옴(주석 2). ↩↩↩↩↩

  13. Serebii.net, “Join Avenue”, 포켓몬스터 블랙2·화이트2 절, 2026년 10월 4일 저장, https://www.serebii.net/black2white2/joinavenue.shtml. 팬 사이트이며 이 기능에 쓴 유일한 출처. ↩↩↩↩↩

  14. Wikipedia, “Death Stranding”, Gameplay 절(자체 출처를 인용), 2026년 10월 4일 저장, https://en.wikipedia.org/wiki/Death_Stranding. ↩↩↩↩↩

  15. Krews, Arcturus Morningstar(팬이 작성한 Habbo 서버의 오픈 소스 에뮬레이터), src/main/java/com/eu/habbo/habbohotel/rooms/Room.java(IDLE_CYCLES = 240, IDLE_CYCLES_KICK = 480, scheduleAtFixedRate(this, 500, 500, TimeUnit.MILLISECONDS)), 2026년 10월 5일 저장, https://git.krews.org/krews/Morningstar/-/blob/master/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java. Sulake가 공개한 수치가 아니라 에뮬레이터의 상수이며, RoomUnit은 읽지 않음. ↩↩↩↩↩↩↩

  16. Glenn Fiedler, “Snapshot Interpolation”, Gaffer on Games, 2014년 11월 30일, 2026년 10월 4일 저장, https://gafferongames.com/post/snapshot_interpolation/. ↩↩↩↩↩↩↩↩

  17. Apple, “Game Center & Privacy”, “Children in Game Center” 절과 친구 절, 2026년 10월 4일 저장, https://www.apple.com/legal/privacy/data/en/game-center/. ↩↩↩↩↩↩↩↩

  18. Apple Developer Documentation, GKLocalPlayer.isMultiplayerGamingRestricted(iOS 13.0+), 문서 JSON을 2026년 10월 4일 저장, https://developer.apple.com/documentation/gamekit/gklocalplayer/ismultiplayergamingrestricted. 인용은 Discussion 본문이며, true에 대한 인라인 참조를 단어로 옮기고, 초안 폴더의 sources/extract_gk.py로 평문화한 것. ↩↩↩↩↩↩↩↩↩↩↩↩

  19. 연방거래위원회(FTC), “Children’s Online Privacy Protection Rule”, 최종 규칙, 연방 관보(Federal Register), 2025년 4월 22일, 문서 2025-05904, 90 FR 16918, 2026년 10월 4일 저장, https://www.federalregister.gov/documents/2025/04/22/2025-05904/childrens-online-privacy-protection-rule. DATES 절과 개정된 16 CFR 312.2(개인정보의 정의), 312.5(c)(7), 312.8, 312.10. 이 규칙이 Kiradex에 무엇을 뜻하는지에 관한 이 글의 모든 서술은 저자의 읽기이며, 법률 자문이 아님. ↩↩↩↩↩↩↩↩↩↩↩

  20. 저자가 읽은 Kiradex 저장소(비공개), 2026년 10월 5일의 작업 트리, 읽기만 했고 git 명령은 실행하지 않음. 파일은 SHA-256의 첫 16진수 8자리로 식별함. server/app/main.py(87c6cc9f. TICK_SECONDS = 0.5, SHOW_EVERY_SECONDS, 메시지 핸들러. 그중 걷는 사람의 id와 타일을 싣는 223번째 줄의 moved 중계), server/app/npcs.py(d84f12a5. 모듈 docstring, STEP_SECONDS = 0.7, 네 가지 정의와 그 경로, 바라는 것, 대사), server/app/rooms.py(dcd66866. 115번째 줄의 Player.public()은 ID, 핸들, 모습을 객체 하나로 반환하며, 도착할 때 main.py 203번째 줄에서 방에 브로드캐스트됨. CAPACITY, MOVES_PER_SECOND, SAY_EVERY_SECONDS, Plaza, 그리고 docstring과 조기 반환이 있는 Room.tick), server/app/lines.py(391f5391. 대사 23개와, 서버가 도착할 때 목록을 보낸다고 말하는 docstring). ↩↩↩↩↩↩↩↩↩↩↩

  21. 저자의 측정, measure_kiradex.py(같은 폴더), 2026년 10월 5일 재실행, 출력은 measure_kiradex.out과 동일. 주석 20의 작업 트리에서 app.lines, app.rooms, app.npcs를 가져오고, 클라이언트 상수는 PlazaRig.swift와 PlazaClient.swift(주석 50)에서 읽음. 정해진 대사 수, 방 정원, 빈도 제한, 틱, 재사용 대기 시간, 광장 크기와 도착 지점, 네 수집가의 경로와 고리 길이, 걷기와 달리기 속도, 6걸음 점프 기준, 재연결 백오프. 이것이 도출한 0.7초와 36퍼센트는 주석 6이 대체함. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  22. Blake Crosley, “Pixel-Art Motion: Walks, Cameras and Doors on iPhone”, blakecrosley.com, 2026년 10월 5일, https://blakecrosley.com/blog/pixel-art-motion-on-iphone. 그 주석 6은 앱의 걷기를 정확한 60Hz로 모델링함. 달리기는 타일당 10프레임, 초당 6.00타일로, 초당 4타일에 달리기 배율 1.6을 곱한 6.4와 비교되며, 120Hz에서는 6.32. 현재 코드에 관한 절은 걸음 도중의 두 번째 탭에 따른 되돌림을 walk(to:)에서 읽어 내고, 그 브리프는 진행 중인 걸음은 결코 초기화하지 않는다는 규칙을 정함. 그 브리프의 항목 1은 60Hz에서 걷기를 타일당 16틱, 달리기를 8틱, 즉 초당 3.75타일과 7.5타일로 정하며, 이것이 시리즈의 움직임 계약임. 그 검사는 -motionLog 실행 인자로 플레이어의 위치를 틱마다 기록함. ↩↩↩↩↩↩↩

  23. Kiradex, docs/WORLD.md(“Kiradex World: research and plan”), 2026년 10월 5일의 작업 트리(SHA-256 c7714bc1), 읽기만 함. 2절(어린이 카테고리와 5.1.4의 길, 9+ 또는 12+ 등급), 3절(Game Center 신원, Screen Time 통제, 팔로워 수 없음, 정해진 대사, 변호사의 읽기), 5절(10월 2일 기준으로 만든 서버: FastAPI와 WebSocket, 존재감만, 아무것도 저장하지 않음, 초당 이동 8회, 기기에서의 숨기기, Declared Age Range에 따른 주니어 모드를 포함한 “Not yet”(아직 아님). 그리고 거기에 이른 근거로, 클라이언트가 위치와 방향을 8~10Hz로 보내는 설계), 그리고 “Decisions taken (Blake, October 2, 2026)”(내린 결정, Blake, 2026년 10월 2일)의 항목 6(등급과 연령대)과 항목 8(자리를 잡을 때까지 변호사 없음). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  24. 저자의 Kiradex 작업 트리 Kiradex/와 server/app/ 검색, 2026년 10월 5일. isMultiplayerGamingRestricted, isUnderage, isPersonalizedCommunicationRestricted, Declared Age Range API, 그리고 방문이나 매일의 대화를 저장하는 것을 찾았으나 일치 없음. ↩↩↩

  25. pret, pokeemerald/include/constants/event_object_movement.h, 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/include/constants/event_object_movement.h ↩

  26. 그림은 글 초안 옆에 둔 figures/make_figures.py로 2026년 10월 5일에 그렸음. measure_npcs.py, measure_npc_cadence.py, measure_chat_trend.py, measure_record_mixing.py, measure_kiradex.py, measure_remote_walk.py의 저장된 출력에서 각 값을 파싱하고, 그리기 전에 기대값과 대조해 확인함. 기대값은 리서치 자료의 것이되, 예외로 마을 인구 그림은 이제 수정한 measure_npcs.py에서 사람만 그리고 숨김 플래그 없는 100명의 줄을 더했으며, 대사집의 열린 단어 940개는 수정한 measure_chat_trend.py에서 나옴. 두 번째 엔진의 검토 뒤 유행 그림은 수정한 measure_chat_trend.py에서 게임 자체의 정수 일 점수를 그리며(연속적인 삼각파가 아니라 경과한 하루마다 한 점), 지나가는 걸음 그림은 수정한 measure_remote_walk.py를 그림. 현재 리그는 자체 32비트 산술로, 버퍼는 움직임 계약의 초당 3.75타일과 7.5타일에 지연 100, 300, 325, 350ms로 그렸음. 대사집 그림은 브리프 제안의 도식이며, 비교 수치는 같은 방식으로 파싱함. 게임 아트, 스프라이트, 맵 렌더링은 하나도 쓰지 않음. ↩↩↩↩↩

  27. pret, pokeemerald/src/event_object_movement.c(IsCoordOutsideObjectEventMovementRange, sMovementDelaysMedium, sMovementDelaysShort, // Unused 주석이 달린 sMovementDelaysLong, 회전하는 두 유형의 48프레임 고정 대기, MovementType_WanderAround_Step4, GetWalkNormalMovementAction, sStep 테이블), 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/event_object_movement.c ↩↩↩↩↩

  28. pret, pokeemerald/include/constants/flags.h(DAILY_FLAGS_START와 매일 플래그 12개), 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/include/constants/flags.h ↩

  29. Stardew Valley Wiki, “Villagers”, oldid 191346, 2026년 2월 26일 편집, 2026년 10월 4일 저장, https://stardewvalleywiki.com/Villagers. 선물을 줄 수 있는 주민 34명은 페이지 목록(독신 남성 6, 독신 여성 6, 결혼 후보가 아닌 22)을 센 것. 위키의 “Friendship” 페이지는 처음부터 있는 마을 사람 28명이라는 다른 집합을 셈. ↩

  30. Stardew Valley Wiki, “Pierre”, 2026년 10월 4일 저장, https://stardewvalleywiki.com/Pierre. 평소 일정, 여섯 가지 일정 변형, 그리고 어느 하트 단계에서든 오는 우편과 하트 3개에서 오는 우편. ↩↩↩

  31. Nookipedia, “Hobby”, 2026년 10월 4일 저장, https://nookipedia.com/wiki/Hobby. ↩

  32. Nookipedia, “Villager”, 게임별 절, 2026년 10월 4일 저장, https://nookipedia.com/wiki/Villager. ↩↩↩↩↩↩↩

  33. Nookipedia, “Villager”, Personalities 절, 2026년 10월 4일 저장, https://nookipedia.com/wiki/Villager#Personalities. ↩

  34. pret, pokeemerald/src/record_mixing.c(struct PlayerRecordEmerald, ReceiveOldManData, ReceiveDewfordTrendData), 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/record_mixing.c ↩

  35. pret, pokeemerald/src/mauville_old_man.c(선택하는 switch ((trainerId % 10) / 2)와 그 주석, ResetHipsterFlag, ResetMauvilleOldManFlag, SetHipsterTaughtWord), pokeemerald/src/record_mixing.c(ReceiveOldManData 끝에 있는 ResetMauvilleOldManFlag의 유일한 호출), 그리고 pokeemerald/data/scripts/mauville_man.inc(setflag FLAG_UNLOCKED_TRENDY_SAYINGS, special HasHipsterTaughtWord), pokeemerald/data/maps/MauvilleCity_PokemonCenter_1F/map.json(스크립트가 MauvilleCity_PokemonCenter_1F_EventScript_MauvilleOldMan인 오브젝트 이벤트), 커밋 731ad5b, 저자가 2026년 10월 4일과 5일에 읽음, https://github.com/pret/pokeemerald/blob/master/src/mauville_old_man.c. 트레이너 ID 끝자리가 2나 3인 게임에서 유행을 좇는 사람으로 시작한다는 것은 switch에 대한 저자의 산수. ↩↩↩↩↩↩

  36. Blake Crosley, “Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew”, blakecrosley.com, 2026년 10월 3일(법률 FAQ는 2026년 10월 4일 업데이트), https://blakecrosley.com/blog/pixel-art-world-on-iphone. 유행어 규칙을 기록 교환으로 유행을 좇는 사람이 올 때마다 하나로 설명하며, 그 법률 FAQ는 Palworld 개발사에 대해 주장된 세 특허(포획 아이템을 조준해 던지기, 포획 가능성 표시, 탈 수 있는 캐릭터에 올라타기)를 자체 출처와 함께 밝힘. ↩↩

  37. pret, pokeemerald/src/dewford_trend.c(trendiness와 maxTrendiness, 그리고 유행 저장에 관한 헤더 주석), 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/dewford_trend.c ↩↩↩↩

  38. 저자의 측정, measure_chat_trend.py(같은 폴더), 2026년 10월 5일 수정 후 재실행, 출력은 measure_chat_trend.out으로 저장. 수정판은 .enabled = FALSE로 표시된 항목을 열린 단어에서 빼고(946개 항목 중 활성 940개), easy_chat_groups.h에서 트레이너 그룹의 numEnabledWords 줄(“Excludes Red, Green, Flame, Gold, Leaf, and Silver”, 레드, 그린, 플레임, 골드, 리프, 실버 제외)을 출력하며, IsEasyChatIndexAndGroupUnlocked의 항목별 규칙을 출력함. 이 규칙에 따라 202개 항목 목록의 이름은 그 종을 보았을 때만 열림. 이전 판과 그 출력은 .r1 접미사로 남겨 둠. 입력: pret pokeemerald 731ad5b의 src/data/easy_chat/(그룹 크기, 비활성 항목, numEnabledWords), src/easy_chat.c(그룹 해금 switch, 항목별 규칙, 문구 격자), include/constants/global.h(MAIL_WORDS_COUNT, NUM_QUESTIONNAIRE_WORDS, SAVED_TRENDS_COUNT), src/dewford_trend.c(SeedTrendRng와 매일의 한 단계). 두 번째 엔진의 검토 뒤 두 번째 수정(이전 판과 그 출력은 .pre-astra 접미사로 남겨 둠)은 정점 분포를 독립 균등 근사라고 명시함. 이는 중첩된 세 번의 Random() % 98 추첨을 각각 0~97에 대한 독립 균등 추첨으로 보고 계산한 것임. 또한 16비트 나머지 가중치(src/random.c의 Random()은 상위 16비트를 반환하므로 나머지 0~71은 65,536번 중 669번, 72~97은 668번 나옴)를 쓴 같은 분포를 더했으며, 평균 62.89와 80 이하 81.34%로 62.90과 81.33%에 대응함(여전히 독립 추첨 가정). 그리고 UpdateDewfordTrendPerDay를 호출 한 번에 경과 하루로 하여 0에서 오르는 점수부터 옮겼으며, 정점 30, 64, 127에서 그 상태로 처음 돌아오는 것은 12일, 128일, 254일 뒤임(도달 가능한 모든 시작 상태에서 같은 주기 길이). 오르내림 길이 12일, 25.6일, 50.8일은 정점의 두 배를 5로 나눈 값으로, 연속적인 근사임. ↩↩↩↩↩↩↩↩↩↩↩↩

  39. pret, pokeemerald/src/union_room_chat.c, pokeemerald/include/union_room.h, pokeemerald/include/constants/global.h, 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/union_room_chat.c. 그곳에서 키보드 입력 채팅이 용인될 수 있었던 이유에 대한 읽기는 저자의 것. ↩

  40. pret, pokeemerald/src/easy_chat.c와 pokeemerald/src/data/easy_chat/, 커밋 731ad5b, 2026년 10월 4일 열람, https://github.com/pret/pokeemerald/blob/master/src/easy_chat.c 및 https://github.com/pret/pokeemerald/tree/master/src/data/easy_chat. ↩↩↩↩

  41. Wikipedia, “Dark Souls (video game)”, Multiplayer(그 출처 [5]를 인용)와 Awards 절, 2026년 10월 4일 저장, https://en.wikipedia.org/wiki/Dark_Souls_(video_game). ↩↩↩↩↩↩

  42. Bandai Namco Entertainment Europe, “Dark Souls Remastered”, Key features, 2026년 10월 5일 저장, https://en.bandainamcoent.eu/dark-souls/dark-souls-remastered. Bandai Namco America의 페이지는 텍스트가 127바이트뿐인 스크립트 껍데기로 저장됨. FromSoftware 자체의 설명에는 닿지 못함. ↩

  43. PlayStation, “Death Stranding”, “Unique social strand gameplay” 절, 2026년 10월 4일 저장, https://www.playstation.com/en-us/games/death-stranding/. ↩

  44. Wikipedia, “Splatoon (video game)”, Gameplay와 Multiplayer 절(자체 출처를 인용), 2026년 10월 4일 저장, https://en.wikipedia.org/wiki/Splatoon_(video_game). Nintendo 자체의 Splatoon 페이지는 가져오지 않음. ↩↩

  45. The Pokémon Company International, pokemon.com 게임 페이지 “Pokémon Black Version 2 and Pokémon White Version 2”와 “Pokémon Sun and Pokémon Moon”, 2026년 10월 4일 저장, https://www.pokemon.com/us/pokemon-video-games/pokemon-black-version-2-and-pokemon-white-version-2 및 https://www.pokemon.com/us/pokemon-video-games/pokemon-sun-and-pokemon-moon. 어느 쪽도 조인 애비뉴나 페스서클을 언급하지 않음. Bulbapedia는 시도하지 않음. ↩

  46. Serebii.net, “Festival Plaza”, 포켓몬스터 썬·문 절, 2026년 10월 4일 저장, https://www.serebii.net/sunmoon/festivalplaza.shtml. 팬 사이트이며 이 기능에 쓴 유일한 출처. ↩↩

  47. Serebii.net, “Gyms”, Pokémon GO 절, 2026년 10월 4일 저장, https://www.serebii.net/pokemongo/gyms.shtml. 팬 사이트이며 이 기능에 쓴 유일한 출처. ↩↩

  48. Solero, Houdini(팬이 작성한 Club Penguin의 오픈 소스 서버 에뮬레이터), houdini/handlers/play/player.py(sp, ss, sj, sma, sl, sg, se 핸들러와 그 재사용 대기 시간. 시각을 잡고 61초 잠든 뒤 마지막 하트비트가 그 시각보다 오래된 모든 클라이언트를 닫는 server_heartbeat(if penguin.heartbeat < timer: await penguin.close(), 저장한 HTML에서 읽었으며, 저장한 텍스트 추출본에서는 이 줄이 깨져 있음). 그리고 플레이어에게 허용된 분을 매분 거꾸로 세고, 7분과 5분에 경고하며, 0에서 연결을 끊는 server_egg_timer), 2026년 10월 4일 저장, https://github.com/solero/houdini/blob/master/houdini/handlers/play/player.py. 원래 클라이언트가 기대한 것의 증거이지, Disney가 공개한 동작이 아님. ↩↩↩↩↩↩↩

  49. billsonnn, Nitro renderer(팬이 작성한 Habbo의 오픈 소스 클라이언트), src/nitro/room/object/logic/MovingObjectLogic.ts(DEFAULT_UPDATE_INTERVAL = 500과 update의 선형 보간), 2026년 10월 4일 저장, https://github.com/billsonnn/nitro-renderer/blob/main/src/nitro/room/object/logic/MovingObjectLogic.ts. ↩↩↩

  50. 저자가 읽은 Kiradex 저장소(비공개), 2026년 10월 5일의 작업 트리, 읽기만 함. Kiradex/World/PlazaRig.swift(SHA-256 819cd8d6. 29번째 줄의 Float인 progress, 101번째 줄의 tilesPerSecond, 526~542번째 줄의 move(other:to:facing:), 614번째 줄에서 플레이어에게만 설정되는 running, 652~672번째 줄의 advance(), 722~727번째 줄의 place()와 그 픽셀 반올림, doze가 들어 있는 idleEmotes 목록), Kiradex/World/TileMap.swift(db4272c2. 60번째 줄의 16인 tileSize와 235번째 줄의 centre), Kiradex/World/PlazaClient.swift(5c491933. 165번째 줄의 재연결 지연), Kiradex/World/PlazaStage.swift(8b4850af. 광장에 적용되는 낮과 밤의 단계, 그리고 149번째 줄의 rig.update(Float(event.deltaTime))), Kiradex/World/Daylight.swift(536350dc. Daylight.Phase와 그 시간 범위, 그리고 기기 자체의 달력과 시간대인 Calendar.current에서 시각을 가져오는 Daylight.now). 서버의 시계도 같은 날 검색함. server/app/main.py와 server/app/rooms.py는 time.monotonic()을 쓰며, server/app 어디에도 시간대를 지정하는 것은 없음. ↩↩↩↩↩↩↩↩↩

  51. Apple Developer Documentation, GKLocalPlayer.isPersonalizedCommunicationRestricted(iOS 14.0+), 문서 JSON을 2026년 10월 4일 저장, https://developer.apple.com/documentation/gamekit/gklocalplayer/ispersonalizedcommunicationrestricted. 주석 18과 같이 인용을 위해 평문화함. 게임 자체의 정해진 대사가 페이지가 게임에게 비활성화하라고 요구하는 “custom communication features”(독자적인 소통 기능)에 들어간다는 것과, 이모트는 들어가지 않는다는 것은 저자의 읽기. ↩↩↩↩↩↩↩↩

  52. Apple Developer Documentation, GKLocalPlayer.isUnderage(iOS 4.1+), 문서 JSON을 2026년 10월 4일 저장, https://developer.apple.com/documentation/gamekit/gklocalplayer/isunderage. 주석 18과 같이 인용을 위해 평문화함. ↩↩↩↩

  53. Apple Support, “Set up parental controls to manage your child’s iPhone or iPad”, “Set restrictions for Game Center” 절, 2026년 10월 4일 저장, https://support.apple.com/en-us/105121. ↩

  54. Apple Newsroom, “Apple expands tools to help parents protect kids and teens online”, 2025년 6월 11일, 2026년 10월 4일 저장, https://www.apple.com/newsroom/2025/06/apple-expands-tools-to-help-parents-protect-kids-and-teens-online/. ↩↩↩

  55. Apple, “App Review Guidelines”, 2026년 6월 8일 최종 업데이트, 가이드라인 1.2, 1.3, 5.1.4, 2026년 10월 4일 저장, https://developer.apple.com/app-store/review/guidelines/. ↩↩↩↩↩↩↩↩

  56. 연방거래위원회(FTC), “FTC Finalizes Changes to Children’s Privacy Rule Limiting Companies’ Ability to Monetize Kids’ Data”, 보도자료, 2025년 1월 16일, 2026년 10월 4일 저장, https://www.ftc.gov/news-events/news/press-releases/2025/01/ftc-finalizes-changes-childrens-privacy-rule-limiting-companies-ability-monetize-kids-data. ↩

  57. 저자가 읽은 Kiradex 서버의 시작 명령과 그것이 돌리는 웹 서버, 2026년 10월 5일, 읽기만 함. server/Procfile(SHA-256 2e883a7e)과 server/railway.toml(17f8a3ba)은 uvicorn app.main:app을 --proxy-headers --forwarded-allow-ips='*'와 함께, 로그 옵션 없이 시작함. server/requirements.txt(71c30c3b)는 uvicorn[standard]==0.37.0을 고정함. 서버 환경에 설치된 그 버전에서 uvicorn/config.py(379e9baf)는 access_log의 기본값을 true로 두고, uvicorn/protocols/http/httptools_impl.py(b6e4010a)와 h11_impl.py(e1bf8ab3)는 그것이 true일 때 요청마다 get_client_addr(self.scope)를 기록하며, uvicorn/protocols/websockets/websockets_impl.py(029977b2)는 uvicorn.error 로거의 INFO로 클라이언트 주소와 함께 '%s - "WebSocket %s" [accepted]'를 기록하는데, 이는 --log-level warning이 잠재움. 코드를 읽은 것이며, 서버를 실행하지 않았고 배포된 로그도 보지 않음. ↩↩

  58. Blake Crosley, “Pixel-Art People: Characters and a Creator on iPhone”, blakecrosley.com, 2026년 10월 3일, 4절(PEGI의 규칙)과 6절, 7절(모습 문자열, 매니페스트, 모든 모습을 그리는 포지), https://blakecrosley.com/blog/pixel-art-characters-on-iphone. ↩↩↩

  59. PEGI, “What do the labels mean?”, 2026년 10월 3일 열람, https://pegi.info/what-do-the-labels-mean, 캐릭터 편 글(주석 58)에서 인용한 것을 따름. 이 글을 위해 다시 가져오지 않음. ↩↩↩

관련 게시물

Pixel-Art Structures: Houses, Halls and Interiors on iPhone

How Pokémon, the SNES RPGs and Stardew build houses, interiors and floors, measured from source, and how Kiradex's town …

80 분 소요

Pixel-Art Routes: Map Connections and Districts on iPhone

How Pokémon, Stardew and Animal Crossing build the land between towns, measured from source: routes, seams, gates, map s…

110 분 소요

iPhone Duo에 맞춰 앱 준비하기: 실제 앱으로 따라가는 예제

iPhone Duo용 앱을 준비하고 제출하는 방법. 레이아웃을 켜는 SDK 스탬프, 모든 포즈로 돌려 본 실제 앱, 스크린샷, App Store 규칙까지 정리했습니다.

53 분 소요