세로 화면 조작부를 요청대로 바꿨다. 회전(↺ ↻ 180°)과 HOLD 를 윗줄로 올리고, 하드 드롭은 맞은편 소프트 드롭과 같은 자리(맨 아래 가로로 긴 낮은 버튼)로 내렸다. 한 줄에 버튼이 4개가 되어 액션 컬럼 폭을 1.1 → 1.35 로 늘렸다. 1.1 로는 버튼이 세로로 길쭉해져 엄지로 누르기 불편했다. 그 과정에서 HOLD 를 눌러도 패널이 그대로인 것을 발견했다. game.hold 와 game.peekNext() 는 Compose 가 관찰하지 못하는 평범한 엔진 필드인데, BoardCanvas 와 달리 패널 쪽은 state.version 을 구독하지 않아 게임 시작 시점의 넥스트 큐가 끝까지 멈춰 있었다. 넥스트 미리보기는 이 장르의 핵심이라 출시 전에 잡아야 하는 버그다. 패널을 SidePanels 로 분리해 version 을 구독시켰다 — 재구성 범위를 패널 서브트리로 좁혀 매 프레임 BoxWithConstraints 측정이 다시 돌지 않게 했다. 검증: 에뮬레이터에서 HOLD 탭 후 패널 유채색 픽셀 0 → 5474, 넥스트 패널은 하드 드롭 뒤 49370 픽셀 변화(수정 전에는 보드가 116848 픽셀 바뀌는 동안 넥스트는 0 픽셀이었다). 회전 3종·하드 드롭·소프트 드롭 모두 동작 확인. 세로/가로 양쪽 확인. 엔진 테스트 28개 통과. 바뀐 조작부가 찍힌 스토어 스크린샷 3장도 다시 촬영했다.
TETRIG (테트리그)
쌓아라, 부숴라, 올라가라.
TETR.IO 스타일의 모바일 실시간 대전 블록 스태커 웹 게임. 솔로 3모드 + 인원 무제한 커스텀 룸(FFA) + Glicko-2 랭크 리그전을 하나의 단일 HTML 클라이언트와 Cloudflare Workers 서버로 구현한다.
이 문서는 앱 제작을 위한 최종 빌드 진입점(entry point) 이다. 모든 설계 결정·수치·에셋의 정본(source of truth)은 TETRIG-최종명세서.md 이며, 구현 시 반드시 명세서와 UX 목업을 함께 열어 작업한다.
배포 경로 (2026-09-24 확정): 출시 대상은 Google Play 안드로이드 네이티브 앱 (
app/, Kotlin Multiplatform + Compose). 아이폰은 App Store 대신 웹 PWA (https://tetrig.pages.dev, "홈 화면에 추가")로 커버한다 — iOS 네이티브는 Apple Developer Program 연 $99 + 맥이 필요해 접었다. 필요한 비용은 Play 개발자 등록비 $25(1회)뿐이고, 웹/서버는 Cloudflare 무료 티어로 간다. 웹 클라이언트 진입점은index.html(빌드 산출물), 소스는src/, 서버 계획(Cloudflare Workers + D1 + WebSocket)은 명세서 그대로.
무엇을 만드는가
산출물은 딱 2개다.
- 클라이언트 — 단일 HTML 파일 (HTML + CSS + JS, Canvas 2D). 외부 에셋 0개, 사운드는 100% Web Audio 신스 생성.
- 서버 — Cloudflare Workers 프로젝트 (wrangler 표준 구조: Durable Objects 2종 + D1 + Cron).
게임 구성:
- 솔로 3모드: SPRINT(40줄 최속) · TIME ATTACK(2분 점수) · ENDLESS(무한)
- 커스텀 룸: 6자리 초대 코드, 인원 무제한 FFA, 호스트 규칙 설정
- 리그전: Glicko-2 레이팅 + 18단계 랭크 + 배치 10경기 + 시즌제
아키텍처
[모바일 브라우저] [Cloudflare]
클라이언트 (단일 HTML, Canvas) ⇄ WSS ⇄ Workers
├ 게임 엔진 (SRS+, 판정, 렌더) ├ MatchmakerDO (리그 매칭 큐, 싱글턴)
├ 솔로 모드 3종 ├ GameRoomDO (경기방 — 리그 & 커스텀 공용)
└ 멀티/커스텀/프로필/리더보드 UI ├ D1 (계정·레이팅·전적·리더보드)
└ Cron (RD 감쇠, 시즌 처리)
- 클라이언트: Cloudflare Pages(또는 Workers 정적 자산)로 호스팅, 시작 주소는
tetrig.pages.dev. - 서버: 무료 티어로 운영 가능 (DO SQLite 백엔드 + WebSocket Hibernation).
- 로컬 개발:
wrangler dev+ 브라우저 탭 여러 개로 다인전 테스트.
저장소 파일
| 파일 | 용도 |
|---|---|
TETRIG-최종명세서.md |
정본 명세서 — 모든 규칙·수치·좌표·스키마·프로토콜 |
tetrig-ux-mockup.png |
9화면 UX 목업 (1390×2944) — UI 구현의 시각 기준 |
tetrig-logo.svg / tetrig-logo.png |
가로형 워드마크 로고 (실사용 에셋 / 렌더 미리보기) |
tetrig-icon.svg / tetrig-icon.png |
앱 아이콘 512×512 (실사용 에셋 / 렌더 미리보기) |
명세서의 화면 번호 ①⑨는 목업 시트의 화면과 1:1 대응한다. 시각 판단은 목업 이미지를 따르고, 명세서 §11§13의 좌표·팔레트 수치로 정확도를 보정한다.
핵심 규칙 요약 (상세는 명세서 §4~§8)
- 보드: 10열 × 40행(내부), 하단 20행 표시. 랜덤 7-bag, 넥스트 5, 홀드 1회/피스, 고스트 피스.
- 회전: SRS + 180° 킥테이블 (명세서 §4.1). 락 딜레이 500ms / 최대 15회 리셋.
- 판정: T-spin / T-spin mini, B2B, 콤보, All Clear. 4줄 클리어는 "QUAD" (Tetris/테트리스 단어 금지).
- 대전: 가비지 전송 테이블 + 상쇄(counter) + 8줄/회 상한 + 장기전 방지 배율.
- 리그: Glicko-2 (R=1500, RD=350, σ=0.06, τ=0.5) → 표시용 TR(0~25,000) → 18단계 랭크. 배치 10경기, RD<100일 때만 랭크 표시.
- 매치메이킹: MatchmakerDO 싱글턴 큐, R ± 2×RD 구간이 겹치면 성사. 형식은 높은 랭크 기준 A+ 이하 FT3 / S- 이상 FT5.
디자인 시스템 (NEOBRUTAL — 상세는 명세서 §13)
- 테마 이원화: 메뉴/로비/결과는 BONE(#EDE7DA) 라이트, 인게임은 INK(#17161B) 다크.
- 컴포넌트: 2px 잉크 보더 + 블러 0 하드 오프셋 섀도 + 우하단 챔퍼컷 + 고대비 플랫 컬러.
- 시그니처: 45° 해저드 스트라이프(가비지·공격·광고·난투에만 절제 사용).
- 금지: 다크 네이비 배경 · 네온 글로우 · 그라디언트 · 블러 섀도 · 과도한 라운딩.
- 피스 7색(v2 확정): I
#3EC1B6/ O#F5C531/ T#A76BF2/ S#7FCC4C/ Z#F25C4C/ J#4C7DF2/ L#F2913D/ 가비지#46464F.
개발 로드맵 (이 순서로 진행)
각 Phase 완료 시 실행 가능한 파일 + 코어 로직 단위 테스트 통과를 완료 기준으로 삼고, 사용자 확인 후 다음으로 넘어간다.
| 단계 | 내용 | 완료 기준 |
|---|---|---|
| Phase 1 | 코어 엔진: 보드·피스·SRS+180 킥·7-bag·홀드·고스트·락딜레이·클리어 | 키보드 플레이 + 킥/판정 단위 테스트 통과 |
| Phase 2 | 판정·점수 + 솔로 3모드 + 솔로 결과 화면 | 3모드 정상 플레이·기록 저장 |
| Phase 3 | 가상 버튼 + 스와이프 + 핸들링 설정 + 메뉴 화면들 | 모바일 브라우저 실기 조작 확인 |
| Phase 4 | 서버 뼈대: 게스트/계정·기록 이전·GameRoomDO·WS·가비지 원장 + 커스텀 1:1 | 탭 2개로 코드 방 1:1 대전 성사 |
| Phase 5 | 커스텀 FFA: 다인 타겟팅·미니뷰 그리드·KO 관전·도중 입장·리매치 투표 | 탭 4개+ FFA 라운드 정상 진행 |
| Phase 6 | MatchmakerDO + Glicko-2 + TR/랭크 + 리더보드·프로필·전적 + 리그 결과 | 매칭→경기→TR 반영 전체 사이클 |
| Phase 7 | 연출·사운드·시즌·광고 슬롯 연결·재접속/부하 점검·폴리시 | v1 배포 (wrangler deploy + Pages) |
개발 환경
# 서버 (Cloudflare Workers)
npm create cloudflare@latest # wrangler 프로젝트 초기화
wrangler dev # 로컬 개발 서버 + WS
wrangler deploy # 배포
# 클라이언트
# 단일 HTML 파일 — 브라우저로 직접 열거나 Pages/Workers 정적 자산으로 서빙
준비물(필요 시점에 사용자에게 확인 — 명세서 §17):
- Google OAuth 클라이언트 ID (Phase 4)
- 애드센스 계정 ID (Phase 7)
배포
네이티브 앱 (Kotlin Multiplatform + Compose Multiplatform)
app/ 이 실제 출시 대상이고, 출시 플랫폼은 Google Play(안드로이드) 하나다.
iOS 타깃은 2026-09-24 에 제거했다 — Apple Developer Program 연 $99 와 맥이
필요해 비용 대비 실익이 없다고 판단했다. 아이폰 사용자는 웹 PWA
(https://tetrig.pages.dev)로 커버한다. 엔진과 UI 전부 commonMain 한 벌이라,
나중에 iOS 가 필요해지면 build.gradle.kts 에 iosTarget 만 다시 넣으면 된다.
app/composeApp/src/
commonMain/ 엔진(engine/) · 모드·타이밍(game/) · UI(ui/) · 저장소(data/)
commonTest/ 엔진 테스트 28개
androidMain/ MainActivity, 매니페스트, 아이콘, SharedPreferences 구현
안드로이드 빌드:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export ANDROID_HOME=$HOME/android-sdk
cd app
cp keystore.properties.sample keystore.properties # 서명 키 경로/비번 입력
./gradlew :composeApp:testDebugUnitTest :composeApp:assembleRelease :composeApp:bundleRelease
# → composeApp/build/outputs/apk/release/composeApp-release.apk
# → composeApp/build/outputs/bundle/release/composeApp-release.aab
Play Console 제출 자산
store/ 에 스토어 등록에 필요한 것이 다 들어 있다.
store/listing.md 앱 이름·설명·데이터보안 설문 답안·콘텐츠 등급 가이드
store/privacy.html 개인정보처리방침 (https://tetrig.pages.dev/privacy 로 배포됨)
store/feature_graphic.png 1024x500 피처 그래픽
store/icon-512-play.png 512x512 스토어 아이콘 (알파 없는 정사각형)
store/shots/0*.png 휴대전화 스크린샷 1080x2400 4장
store/shots/tablet_*.png 태블릿 세로 1600x2560 2장 + 가로 2560x1600 1장
store/make_feature_graphic.py, store/make_play_icon.py 자산 재생성 스크립트
앱은 INTERNET 권한조차 요청하지 않는다(aapt2 dump badging 으로 확인).
그래서 Play 데이터 보안 설문은 전 항목 "수집 안 함"으로 끝난다.
기기 호환
고정 해상도를 전제하지 않고 BoxWithConstraints 로 실제 가용 크기에서
셀 크기·UI 배율을 계산한다 (ui/Responsive.kt).
| 폼팩터 | 동작 |
|---|---|
| 일반 폰 세로 | 보드 위 + 조작 아래 |
| 갤럭시 Z 플립 본화면(초세로) | 보드를 세로로 더 키움 |
| 갤럭시 Z 플립 커버(소형) | 축소 배율 + 넥스트 3개로 감축 |
| 갤럭시 Z 폴드 펼침(정사각형에 가까움) | 넓은 폭 레이아웃, 카드 가로 배치 |
| 태블릿 가로 | 보드 가운데, 조작을 좌우 엄지 영역으로 분리 |
접었다 펴기·회전은 액티비티 재시작 없이 처리한다
(configChanges + resizeableActivity=true).
버전 관리
app/gradle.properties의appVersionName(semver) /appVersionCode(단조 증가)가 원본- 빌드 시
-PappVersionName-PappVersionCode로 덮어쓸 수 있다 - 릴리스는 스크립트 한 방:
scripts/release.sh patch # 또는 minor / major
버전 상승 → 테스트 → 서명 빌드 → 커밋 → vX.Y.Z 태그 → Gitea push →
릴리스 생성 + APK/AAB 업로드까지 자동으로 한다.
웹 (Cloudflare Pages)
index.html 기반 PWA 는 그대로 유지한다 (아이폰 사용자용 / 데모).
dist/privacy.html 은 개인정보처리방침이고 /privacy 로 서빙된다.
node build.js
wrangler pages deploy dist --project-name tetrig --branch main
구현 가드레일 (반드시 지킬 것)
- IP 금지: 사용자 노출 텍스트에 "Tetris/테트리스" 금지, 테트리스 음악(Korobeiniki 등) 금지, 표준 가이드라인 색상 1:1 복제 금지, TETR.IO 에셋·명칭 복제 금지.
- BRAND 상수화: 앱 이름·로고 문구는 코드 1곳(
const BRAND = "TETRIG"/BRAND_KO = "테트리그")에서 관리해 1분 내 리브랜딩 가능하게. - 튜닝 수치 상수화: 중력·TR 커브·콤보 테이블 등 조정 대상은 코드 상단 상수로 분리.
- 언어: 사용자 노출 텍스트는 한국어 (게임 용어 SPRINT/HOLD 등 영문 허용).
- 테스트 필수: 킥테이블·T-spin 판정은 반드시 jsdom/node 단위 테스트로 검증.
- 명세 준수: 명세서에 값이 명시된 것은 그대로 구현하고, 없는 것만 질문한다.
라이선스 / 상표
앱 이름 "테트리그(TETRIG)"는 상표 유사성 리스크를 인지한 뒤 스토어 출시·수익화(광고 게시)까지 최종 확정한 이름이다(2026-08-21). 이름 재검토 트리거는 없다. BRAND 상수화는 리브랜딩 대비가 아니라 표기 일관성·유지보수를 위한 단일 소스로서 유지한다 (명세서 §2).