TL;DR
- Stelline은 매일 같은 퍼즐을 제공해야 하는 조건에서 7,000줄 규모의 Swift 엔진을 Kotlin으로 다시 구현하지 않고 Android에 그대로 컴파일해 iPhone과 Android에서 동일한 퍼즐 생성을 구현함.
- Swift 6.4 Android SDK로 SwiftPM 패키지를
aarch64와x86_64Android용으로 교차 컴파일했으며, 엔진은 수정 없이 첫 컴파일에 성공함. - FoundationEssentials, 정적 링크 설정 조정, JSON 기반 JNI 12개 함수를 사용해 라이브러리 크기를 71MB에서 12MB로 줄이고 Kotlin·Jetpack Compose 기반 네이티브 UI를 구성함.
- 1,700개 기준 일일 보드를 Android 기기에서 재생성해 비교하는 회귀 테스트를 Android 9·16·17 에뮬레이터와 5년 된 휴대전화에서 통과함.
- 보급형 기기에서 가장 큰 2성 보드는 생성에 3초 넘게 걸릴 수 있으며, 이때는 미리 저장된 가장 가까운 크기의 보드로 대체함.
Swift 엔진 하나, 두 대의 휴대전화: Stelline의 Android 출시 과정
- Stelline은 스타 배틀(Star Battle) 방식의 논리 퍼즐로, 각 행·열·색상 영역에 퀸 하나 또는 별 두 개를 배치하되 서로 맞닿지 않게 해야 함.
- 퍼즐북과 달리 모든 보드는 휴대전화에서 생성되며, 정확한 해법 탐색기로 해답이 정확히 하나뿐임을 검증함.
- 같은 엔진이 17가지 기법으로 구성된 추론 단계를 사용해 보드의 난도를 평가하며, 힌트는 해당 단계를 하나씩 안내함.
- 2026년 9월 Android 버전을 시작할 때 엔진을 어떻게 활용할지 검토했으며, 엔진은 생성기·해법 탐색기·추론 엔진·일일 보드 규약을 포함한 Swift 코드 약 7,000줄로 구성됨.
- Android에서 흔히 선택하는 Kotlin 이식 대신 같은 Swift 소스를 두 플랫폼에서 사용하는 방식을 택했으며, 이 글은 그 이유와 비용을 설명함.
- Google Play 출시일인 2026년 10월 2일에 두 휴대전화에서 표시한 오늘의 보드임.
일일 보드가 결정 요인임
- Stelline은 매일 모두에게 같은 오늘의 보드를 제공하며, 보드는 퀸 하나를 놓는 9×9와 별 두 개를 놓는 10×10으로 구성됨.
- 서버는 없으며, 보드는 날짜에서 파생한 시드를 바탕으로 최대 96회 생성한 뒤 목표 난도에 처음 도달한 보드를 선택하는 순수 함수임.
- 목표 난도 도달 여부는 추론 엔진의 판정이므로, 당일 보드는 생성기와 난도 평가기의 모든 코드에 달려 있음.
- Kotlin으로 이식했다면 Android와 iPhone 사용자가 같은 날 다른 보드를 받지 않도록 모든 동작을 비트 단위로 영구히 재현해야 했음.
- 모든 정수 연산에서 일치해야 하는 7,000줄 프로그램을 두 구현으로 유지하는 것은 지키기 어려운 유지보수 약속이며, 두 휴대전화에서 같은 소스를 컴파일하면 일치가 구조적으로 보장됨.
2026년 Android에서의 Swift
- Android용 Swift SDK는 공식 SDK이며, 빌드에는 Swift 6.4를 사용함.
- SDK는 Mac에서 SwiftPM 패키지를
aarch64및x86_64Android용으로 교차 컴파일하며, 기반 계층으로swift-foundation을 사용함. - 엔진은 첫 시도에서 수정 없이 8초 만에 컴파일됨.
- 실행 단계에서는 다음과 같은 세부 사항을 해결하는 데 저녁 한나절이 걸림.
- Foundation 크기: 전체 Foundation을 정적으로 링크하면 라이브러리 크기가 71MB였고, 대부분은 ICU가 차지함. 엔진에 필요한 JSON·날짜·파일 기능은 모두
FoundationEssentials모듈에 포함되므로 임포트를 바꾸고 문자 집합 기준 트리밍과 퍼센트 디코딩 등 Foundation 전용 호출 세 개를 자체 코드 20줄로 대체함. 그 결과 라이브러리는 디스크에서 12MB, 앱 전체 다운로드는 약 6MB가 됨. - Dispatch 링크: 정적 링크 상태에서
dlopen시_dispatch_main_q심볼을 찾지 못하는 문제가 발생했으며, 링크 명령에-ldispatch -lBlocksRuntime을 추가해 해결함. - 16KB 메모리 페이지: Google Play는 네이티브 라이브러리가 16KB 메모리 페이지에서 작동할 것을 요구함. NDK r30은 기본적으로 정렬하지만, 링커 플래그를 명시하고 빌드 스크립트에서 모든
.so파일을 확인하며 16KB 에뮬레이터 이미지에서 앱을 실행함. - JSON 브리지: Swift 타입을 Kotlin에 노출하는 대신, 엔진은 세션 열기·탭 적용·힌트 요청·저장 등을 처리하는 JNI 함수 12개를 통해 JSON으로 통신함. 진행 중인 게임은 Swift 측의 정수 핸들 뒤에 유지되므로 탭 입력마다 보드 전체를 다시 디코딩하지 않고 작은 스냅샷 하나만 처리함. 저장 게임 문서는 iPhone 앱이 기록하는 문서와 동일함.
- 네이티브 UI: Kotlin과
Jetpack Compose로 보드용Canvas하나와 산술 기반 터치 위치 판별, 시스템 뒤로 가기 제스처, 모든 칸을 읽는 TalkBack, 7개 언어의 앱별 언어 설정을 구현함. SwiftUI 앱의 UI는 에뮬레이션하지 않음.
검증
- 같은 소스를 사용해도 정수 폭, 플랫폼 수학 라이브러리, 컴파일러 플래그에 따라 결과가 달라질 수 있음.
- Mac 명령줄 도구로 850일분의 두 게임 모드를 포함한 일일 보드 1,700개를 생성하고, 결과의 모든 필드를 기준 데이터로 기록함.
- 휴대전화에서 계측 테스트를 실행해 각 보드를 다시 생성하고 기준 데이터와 비교함. 테스트는 Android 9·16·17 에뮬레이터와 5년 된 휴대전화에서 통과함.
- 어느 날 결과가 달라지는 경우에도 Android 보드는 유효하고 해답이 하나이며 난도 평가를 거친 보드임. 보드 공유 링크에는 영역 정보 자체가 포함되며, Android 1.0에는 서로 다른 보드가 왜곡할 리더보드도 없음.
- 이 테스트는 회귀 방지 장치이지 증명은 아니며, 그 수준이면 충분함.
휴대전화에서의 비용
- 2019년 플래그십 칩에서 새 8×8 보드 생성에는 32ms, 별 두 개를 놓는 11×11 보드에는 1.3초, 일반적인 날의 별 두 개짜리 일일 보드에는 0.7초가 걸림.
- 보급형 휴대전화는 2~4배 느리므로 가장 큰 별 두 개짜리 보드는 3초 제한을 넘을 수 있음.
- 제한을 넘으면 이미 저장된 보드 중 크기가 가장 가까운 보드를 대신 사용하고 토스트 메시지로 알림. 이는 iPhone과 같은 규칙임.
- 오늘의 보드는 앱 시작 시 별도 스레드에서 생성하며, 다음 날 보드도 미리 생성함.
다시 한다면
- 엔진의 기준 소스가 하나이고, 거의 400개 테스트를 Mac에서 1분 만에 실행하며, 난도 평가기 변경을 한 번의 커밋으로 두 휴대전화에 반영할 수 있으므로 같은 방식을 다시 선택함.
- 비용은 링커 동작을 추적하는 저녁 한나절과 Swift 6.4의 빌드 산출물 위치를 아는 빌드 스크립트임.
- 앱의 가치가 결정론적 코어에 있다면 Android용 Swift SDK가 이를 구현할 준비가 된 상태임.
- Stelline for Android는 2026년 10월 2일부터 Google Play에서 제공되며 iPhone 버전과 같은 게임임. 가격은 미화 1.99달러의 일회성 결제이며 광고·계정·인터넷 권한이 없음.
- 자세한 정보: stelline.page. 문의: hello@andelis.studio.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요