궤도 반경 = 박자: ORBIT RUSH v3의 규칙과 별자리 생성기
ORBIT RUSH는 3.0.0에서 핵심 구조를 전부 갈아엎었다. 남은 것은 우주 배경과 음악 엔진 정도이고, 플레이어가 화면에서 박자를 읽는 방법 자체가 바뀌었다. 이 글은 왜 그렇게 했는지, 그리고 "궤도 반경이 곧 박자"라는 한 줄짜리 규칙을 시드 기반 별자리 생성기로 옮기면서 어떤 숫자를 정했는지 정리한 기록이다.
v3에서 메카닉을 전부 바꾼 이유
1.0과 2.0의 ORBIT RUSH는 화면 연출을 빠르게 쌓아 올린 버전이었다. 프로시저럴 음악 엔진, 피버, 엔드리스, 데일리가 1.0에 들어갔고 2.0에서는 워프 배경, 거대 행성, 비트 터널, 줌 펀치 같은 시각 효과가 붙었다. 문제는 그 아래의 기본 규칙이었다. 두 행성이 서로를 축으로 공전하고, 회전 각도가 박자를 뜻하고, 방향 반전이나 속도 타일이 꺾인 선 위에 놓이는 구조는 이미 잘 알려진 리듬 게임 구성과 겹쳤다.
연출을 아무리 바꿔도 손에 남는 감각이 같다면 그건 우리 게임이라고 하기 어렵다. 그래서 v3에서는 겹치는 구성요소 네 가지(상호 공전, 각도=박자, 방향 반전·속도 타일, 꺾인 선 트랙)를 하나도 남기지 않기로 했다. 대신 들어간 것이 궤도 반경=박자, 노드 그래프 형태의 트랙, 그리고 중력·분열·블랙홀이라는 특수 노드다. README의 저작권 메모에도 같은 목록을 적어 두었고, 저장소 규칙으로 "유사 구조로 되돌리지 않는다"를 명시했다. 나중에 새 기능을 넣다가 무심코 예전 구조로 미끄러지는 일을 막기 위해서다.
박자를 각도가 아닌 크기로 읽게 하기
새 규칙을 고를 때 기준은 단순했다. 입력은 여전히 버튼 하나로 두고, 다음 입력까지 남은 시간을 화면만 보고 미리 알 수 있어야 한다. 각도로 박자를 읽는 방식은 위성이 지금 어디쯤 있는지 계속 눈으로 쫓아야 한다. 크기는 그렇지 않다. 다음 별의 궤도가 작은지 큰지는 위성이 도착하기 전부터 보인다.
궤도는 세 단계만 두었다. track.js의 정의는 다음과 같다.
const RING={1:{r:.55,beats:.5},2:{r:.9,beats:1},3:{r:1.35,beats:2}};
const NODE_GAP=.42;
반지름은 0.55, 0.9, 1.35이고 박자는 ½, 1, 2다. 박자는 4배 차이가 나는데 반지름은 약 2.45배 차이밖에 나지 않는다. 반지름을 박자에 정비례시키면 2박 궤도가 ½박 궤도의 네 배가 되어, 한쪽에 맞추면 다른 쪽이 화면을 덮거나 점처럼 작아진다. 세 단계를 구분하는 데 필요한 것은 정확한 비율보다 서로 확실히 다르게 보이는 크기였고, 그래서 반지름은 대략 1.6배씩 커지도록 잡았다.
이 선택에는 부수 효과가 있다. 위성이 한 궤도에서 도는 각도의 분포는 크기와 상관없이 같으므로, 작은 궤도에서는 위성이 눈에 띄게 빨리 돈다. 반지름×회전량÷박자로 선속도를 대략 계산하면 작은 궤도가 큰 궤도보다 1.6배쯤 빠르다. 덕분에 "작다 = 빠르다"는 직관이 크기와 속도 두 경로로 동시에 전달된다.

별자리 생성기: 회전량과 겹침 검사
트랙은 손으로 찍지 않고 시드 하나로 생성한다. 기본 5트랙도 각자 고정 시드(101, 202, 303…)를 가지고 있어 누가 열어도 같은 별자리가 나온다. 생성 순서는 이렇다. 현재 별의 궤도 크기와 종류를 정하고, 위성이 들어온 각도에서 얼마나 돌고 나갈지(회전량)를 고른 뒤, 나가는 방향으로 다음 별을 놓는다.
회전량은 150°에서 300° 사이를 30° 간격으로 고른다. 회전량이 너무 작으면 위성이 거의 직진하는 것처럼 보여 궤도를 돈다는 느낌이 약해지고, 한 바퀴에 가까울수록 나가는 방향이 들어온 방향과 비슷해져 다음 별이 이전 별 쪽으로 붙기 쉽다. 그 사이의 범위를 쓴 것이다. 다음 별까지의 거리는 두 반지름의 합에 NODE_GAP 0.42를 더한 값이라서 궤도끼리 맞닿지 않으면서 발사 궤적이 짧게 유지된다.
남은 문제는 별자리가 자기 자신과 겹치는 경우다. 회전 방향이 한쪽으로 고정되어 있으니 회전량이 계속 크게 나오면 트랙이 말려 들어간다. 생성기는 후보 위치를 최근 16개 별과 비교해, 반지름 합에 0.25를 더한 거리보다 가까우면 회전량을 다시 뽑는다. 이 재시도는 최대 10번이고, 10번째에는 그냥 받아들인다. 완벽한 비겹침을 보장하려면 백트래킹이 필요한데, 16개보다 오래된 별은 대개 카메라가 이미 지나간 뒤라 겹쳐도 읽기에 방해가 적다. 그래서 검사 범위를 최근 구간으로 한정하고 백트래킹은 넣지 않았다.
노드 그래프를 시간표로 바꾸기
별자리를 다 놓으면 각 별에 박자 정보를 붙인다. 별마다 위성이 도착하는 박(arriveBeat), 입력해야 하는 박(pressBeat), 실제로 떠나는 박(launchBeat)이 계산되고, 이것을 이어 붙인 것이 판정에 쓰는 히트 목록이다. 일반 별은 탭 하나, 분열 별은 ½박 간격의 탭 둘, 블랙홀은 누름과 뗌 한 쌍을 만든다.
위성의 화면 위치도 같은 데이터에서 나온다. 현재 박자에서 도착 박을 빼고 궤도의 박 수로 나눈 진행률에 회전량을 곱하면 각도가 된다. 렌더러와 판정이 같은 시간표를 보기 때문에 "위성이 마커에 닿는 순간"과 "판정 기준 시각"이 어긋날 수 없다. 리듬 게임에서 그림과 소리가 따로 노는 것만큼 신뢰를 깎는 버그는 없어서, 처음부터 이 구조를 강제했다.
엔드리스는 같은 생성기를 쓰되 32노드마다 BPM을 5%씩 올리고(최대 300), 템포 변화 지점을 목록으로 남겨 박자와 초를 구간별로 변환한다. 진행도에 따라 작은 궤도의 가중치가 0.25에서 0.55까지 오르고 특수 노드 확률도 최대 2배가 된다. 템포와 밀도가 함께 올라가는 셈이다.
트랙별 난이도 곡선
기본 5트랙은 궤도 크기 가중치와 특수 노드 확률만 다르게 준다. FIRST ORBIT은 중간 궤도 60%, 작은 궤도 15%이고 특수 노드가 없다. EVENT HORIZON은 작은 궤도 45%에 중력 12%, 분열 15%, 블랙홀 12%가 섞인다. 처음 세 노드는 항상 일반 노드로 고정해 시작 직후 갑자기 블랙홀을 만나는 일이 없게 했다. 또 분열 노드는 ½박 궤도에 놓이지 않도록 막았다. ½박 궤도에서 쌍둥이가 ½박 앞서면 도착하자마자 탭해야 해서 사실상 읽을 시간이 없다. 블랙홀이 ½박 궤도에 걸리면 1박 궤도로 키운다.
이런 예외는 규칙표에는 드러나지 않지만, 생성기가 "읽을 수 있는 악보"만 내놓도록 하는 안전장치다. 무작위성은 패턴의 다양성에만 쓰고 공정성은 코드가 지키게 하는 것이 원칙이었다.
정리
v3의 목표는 버튼 하나짜리 리듬 게임에서 우리만의 읽는 법을 만드는 것이었다. 궤도 크기 세 단계, 150~300°의 회전량, 최근 16개 별과의 겹침 검사, 그리고 렌더와 판정이 공유하는 하나의 시간표가 그 결과다. 아직 남은 과제는 별자리 모양 자체를 음악 구조(마디, 프레이즈)와 맞추는 일이다. 지금은 박자 길이만 맞을 뿐, 8마디 프레이즈의 시작이 별자리에서 눈에 띄지는 않는다.