TL;DR

  • Stelline은 매일 같은 퍼즐을 제공해야 하는 조건에서 7,000줄 규모의 Swift 엔진을 Kotlin으로 다시 구현하지 않고 Android에 그대로 컴파일해 iPhone과 Android에서 동일한 퍼즐 생성을 구현함.
  • Swift 6.4 Android SDK로 SwiftPM 패키지를 aarch64와 x86_64 Android용으로 교차 컴파일했으며, 엔진은 수정 없이 첫 컴파일에 성공함.
  • 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_64 Android용으로 교차 컴파일하며, 기반 계층으로 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.