ENDLESS, DAILY RUN, 그리고 설명서 없는 첫 판
FLIP에는 모드가 두 개 있지만 게임 코드 안에서 둘의 차이는 시드 문자열 하나뿐이다. 그리고 처음 온 사람에게 규칙을 알려 주는 별도의 튜토리얼 스테이지는 없다. 대신 첫 판 위에 세 줄짜리 안내를 겹쳐 띄우고, 플레이어가 실제로 해 볼 때마다 다음 줄로 넘어간다. 이 글은 두 모드의 구조와 그 안내가 어떤 조건으로 진행되는지를 코드 기준으로 정리한다.
모드 두 개, 생성기 하나
게임을 시작하는 함수는 하나다. 모드에 따라 월드를 만들 때 넘기는 시드만 바뀐다.
w=WORLD.create(m==='daily'?'flip-daily-'+key:RNG.randomSeed());
ENDLESS의 시드는 현재 시각과 무작위 수를 36진수로 이어 붙인 문자열이라 판마다 다르다. DAILY RUN은 flip-daily-2026-10-13 같은 날짜 문자열이 시드다. 시드가 같으면 장애물과 별의 배치가 처음부터 끝까지 같고, 생성이 프레임 속도와 무관하게 높이 기준으로 이루어지므로 기기가 달라도 같은 코스가 나온다. 모드 차이를 시드에만 둔 덕분에 물리·충돌·점수 규칙은 두 모드에서 완전히 같고, 한쪽에서 고친 버그가 다른 쪽에 그대로 반영된다.
R 키나 결과 화면의 RETRY는 같은 모드를 다시 시작한다. ENDLESS에서는 새 시드로 새 터널이 나오고, DAILY RUN에서는 같은 날짜 시드로 같은 터널을 다시 달린다. 하루 동안 같은 코스를 반복하면서 패턴을 익히는 것이 DAILY RUN의 기본 흐름이 된다.
날짜 번호와 현지 시간
DAILY RUN에는 번호가 붙는다. 기준일은 2026년 10월 7일이고, 그날이 #1이다. 날짜 키는 기기의 현지 날짜로 만든다. 그래서 한국에서는 자정에 새 코스로 바뀌고, 시간대가 다른 지역에서는 그 지역의 자정에 바뀐다. 같은 번호의 코스는 어디서든 같지만, 특정 순간에 두 사람이 서로 다른 번호를 보고 있을 수는 있다. 서버 없이 정적 파일만으로 배포하는 구조에서 "오늘"을 정하는 가장 단순한 방법이고, 랭킹 서버가 없는 지금 구조에는 이 방식으로 충분하다.
번호 계산은 날짜 키를 다시 연·월·일로 나눠 UTC 자정끼리 뺀다. 현지 시각을 그대로 빼면 서머타임이 있는 지역에서 하루가 23시간이나 25시간이 되어 반올림 오차가 생길 수 있는데, 날짜만 떼어 UTC로 비교하면 그런 문제가 없다.
기록은 세 군데에 나눠 저장한다
FLIP은 계정이 없고 모든 기록을 브라우저 localStorage에 둔다. 키는 세 종류다. flip.settings에는 사운드, 모션 줄이기, 튜토리얼 완료 여부가 들어간다. flip.stats에는 최고 점수와 최고 거리, 플레이 횟수, 지금까지 모은 별 합계가 들어간다. 그리고 DAILY RUN을 한 날마다 flip.daily.YYYY-MM-DD 키가 하나씩 생기고, 그날 최고 점수를 낸 판의 점수·거리·별이 저장된다. 같은 날 더 낮은 점수를 내면 덮어쓰지 않는다.
타이틀 화면은 이 값을 읽어 DAILY RUN 버튼 아래에 "#7 · 최고 1,234" 같은 형태로 오늘의 번호와 기록을 보여 준다. 기록이 하나도 없을 때는 "아직 기록이 없어요. 탭 한 번이면 시작!"이 나온다. 결과 화면의 SHARE는 모드와 번호, 거리, 별, 점수를 세 줄 텍스트로 만들어 시스템 공유 시트에 넘기고, 공유 기능이 없는 브라우저에서는 클립보드에 복사한다. 이미지 카드는 설계 문서 로드맵의 1.1.0 항목으로 남아 있다.
첫 판 위에 겹치는 3단계 안내
첫 방문자는 타이틀에서 "처음이세요? 시작하면 화면에서 바로 알려 드려요"라는 한 줄을 본다. ENDLESS든 DAILY RUN이든 시작하면 HUD 아래에 노란 안내 상자가 뜬다. 안내는 시간 대신 행동으로 넘어간다.
if(guide.step===0&&w.flips>0){ guide.step=1; ... }
else if(guide.step===1){ if(w.onWall) guide.wallT+=dt; if(guide.wallT>3){ guide.step=2; ... } }
else if(guide.step===2&&w.braking){ guide.step=3; settings.tutorialDone=true; ... }
첫 단계는 "화면을 탭하면 중력이 반대 벽으로 뒤집혀요"이고, 한 번이라도 탭하면 끝난다. 이 단계에서는 화면 가운데에 TAP이라는 글자와 함께 숨 쉬듯 커졌다 작아지는 원이 그려진다. 첫 장애물은 출발 지점에서 30m 위에 놓이므로 속도 ×1.0 기준으로 약 2.7초의 여유가 있다. 둘째 단계는 벽 가속 규칙을 설명하고, 벽에 붙어 있는 시간이 누적 3초를 넘기면 넘어간다. 셋째 단계는 BRAKE 버튼을 알려 주고, 브레이크를 실제로 한 번 걸면 안내가 끝나며 완료 표시가 저장된다.
각 단계의 조건이 "읽었다"가 아니라 "해 봤다"라는 점이 이 안내의 핵심이다. 원버튼 게임의 규칙은 글보다 손이 먼저 이해한다. 탭 한 번이면 기체가 떨어지는 모습이 보이고, 벽에 3초 붙어 있으면 HUD의 배율 숫자가 올라가는 것이 보인다. 안내 문장은 방금 일어난 일을 설명하는 자막 역할을 한다.
안내가 끝나는 다른 길과 남은 빈틈
세 번째 단계에서 브레이크를 끝내 누르지 않는 사람도 있다. 브레이크 없이도 게임은 충분히 굴러가기 때문이다. 그래서 판이 끝났을 때 중력 반전을 세 번 이상 한 상태라면 안내를 완료로 처리한다. 반대로 탭을 거의 하지 못하고 첫 장애물에 부딪힌 사람에게는 다음 판에서 안내가 처음부터 다시 나온다. 안내 상태는 판마다 0단계에서 새로 시작하고, 완료 표시만 저장되기 때문이다.
코드를 다시 보며 확인한 빈틈도 있었다. 렌더러는 첫 단계의 TAP 강조만 그리고, 둘째·셋째 단계에 넘기는 힌트 값(wall, brake)은 화면에 아무것도 그리지 않았다. 모션 줄이기 설정을 켜 둔 사람에게는 TAP 강조도 나오지 않아 안내 상자의 문장만 남았다.
1.0.2에서 고쳤다. 렌더러에 힌트 전용 함수를 두고 세 값을 모두 그린다. wall 단계에서는 기체가 끌리는 쪽 벽을 금색으로 밝히고, 벽에 붙어 있으면 "WALL ▲ SPEED UP", 건너는 중이면 흐린 색으로 "AIR ▼ SLOW"를 기체 아래에 띄운다. 벽 3초라는 진행 조건을 화면이 직접 가리키는 셈이다. brake 단계에서는 BRAKE 버튼의 화면 위치를 읽어 그 둘레에 푸른 고리와 "HOLD ▶" 표시를 그린다. 모션 줄이기를 켜면 세 표시 모두 숨 쉬는 움직임 없이 고정된 밝기로 나온다. 둘째 단계 안내 문장도 "고득점 비결" 대신 "공중에선 보라 블록에 부딪히니 꼭 필요할 때만 건너세요"로 바꿨다. 속도는 점수에 들어가지 않기 때문이다.
규칙을 다시 보고 싶을 때는 타이틀의 HOW TO PLAY 카드나 일시정지 화면의 "? HOW TO PLAY"를 열면 된다. 일시정지 중에 연 경우 OK를 누르면 게임으로 바로 돌아가지 않고 일시정지 화면으로 돌아가서, 준비가 된 뒤 RESUME을 누를 수 있다.
정리
FLIP의 두 모드는 시드 문자열 하나로 갈리고, 나머지 규칙은 모두 공유한다. DAILY RUN은 현지 날짜 시드와 2026년 10월 7일 기준 번호, 날짜별 최고 기록으로 "오늘의 코스"를 만든다. 첫 플레이 안내는 탭, 벽 3초, 브레이크라는 세 가지 행동을 조건으로 진행되고, 반전 세 번 이상이면 끝난 것으로 본다. 1.0.2부터는 세 단계 모두 화면에 강조 표시가 그려지고, 모션 줄이기 사용자에게도 고정된 표시로 나온다.