한칸은 어떤 앱인가

현실에서는 아직 없어도, 홈 화면에는 내 집 하나쯤.

  • 위젯이 본체다. 앱은 집을 관리하고 꾸미는 보조 공간이고, 사용자가 매일 보는 건 홈 화면 한 칸에 놓인 집이다.

  • 첫날 작은 Starter Tiny Home을 받고, 30일 동안 6단계 여정(J1–J6)에 걸쳐 현관 조명·우편함·화단·데크·나무 같은 10개 역할의 오브젝트를 시간 보상으로 받아 설치한다. 재화는 벽돌(Brick) 하나뿐이다.

  • 현실의 시간(낮·저녁·밤)과 날씨(맑음·흐림·비·눈)가 위젯 속 집에 반영되고, 가끔 고양이·새·택배 같은 작은 장면(Ambient)이 나타난다. 목표는 “오늘 집이 조금 다르네?”라는 느낌이다.

  • 위젯에서 실시간 3D를 돌리지 않는다. Blender에서 시간·날씨·오브젝트별 레이어를 미리 렌더해 두고, 앱의 합성기가 현재 상태에 맞게 겹쳐 한 장의 그림을 만들어 RemoteViews 위젯에 게시한다.

문제 1. 런처를 강제 종료하면 위젯이 죽는다

문제 발생

갤럭시 S25(One UI 8.5)에서 런처를 강제 종료하자 위젯이 “위젯을 불러올 수 없음” 상태로 바뀌었다. 위젯을 탭해도 복구되지 않았다.

원인 식별

이미지를 위젯에 넘기는 방식별로 실험(P1)해 봤다. URI 권한을 부여(grant)하는 방식만 깨졌고, 공개된 읽기 전용 Provider 방식과 Bitmap 방식은 모든 시나리오에서 살아남았다. 문제는 권한 부여 방식에 있었다.

문제 해결

grantUriPermission과 권한 회수 코드를 걷어내고, 공개된 읽기 전용 Provider 하나로 정리했다(결정 D-13). Provider는 filesDir/renders 아래 정해진 형식의 파일만 내주도록 이름과 정규 경로를 엄격하게 검사한다.

재발 방지

테스트 세 개를 추가했다.

  • Provider가 별도 권한 없이 공개되어 있고 권한 부여에 의존하지 않는지

  • 위젯을 게시할 때 grantUriPermission이 호출되지 않는지

  • renders 밖을 가리키는 심볼릭 링크를 거부하는지

문제 2. 위젯 그림이 흐릿하다

문제 발생

S25에서 1128×583 크기의 위젯에 1058×546 그림이 늘어나 표시되고 있었다.

원인 식별

기기 메모리에 맞춰 렌더 해상도 등급을 고르는데, 필요 메모리를 '캔버스 × 32'로 추정하고 있었다. 이 추정이 너무 보수적이어서 앱 힙이 256MB인 S25에서도 가장 작은 등급(S)으로 떨어졌다.

문제 해결

축소할 때 float 캔버스를 그대로 재사용하고 결과를 바로 출력 버퍼로 인코딩하게 바꿨다. 실제 최대 메모리는 'float 캔버스 + 출력 int 버퍼'로, 가로형 L 기준 약 50MB다. 이제 이 실측 피크로 등급을 고른다. 스냅샷 예산에는 전후 버퍼까지 넣어 계산한다.

재발 방지

테스트를 추가했다.

  • 제자리 축소 결과가 기존 두 버퍼 방식과 비트 단위로 같은지

  • 합성 피크 계산식이 '캔버스 float + 출력 int'인지

  • S25 예산에서 L 등급이 유지되어 늘려 그리지 않는지

  • 예산이 작으면 여전히 S 등급으로 내려가는지

문제 3. Pin으로 추가한 위젯이 '수동 설치'로 기록된다

문제 발생

앱에서 Pin 요청으로 추가한 위젯이 계측에서 수동 설치로 집계되고 있었다.

원인 식별

삼성 런처는 Pin 성공 콜백보다 위젯의 onUpdate를 먼저 보낸다. 앱은 먼저 도착한 onUpdate를 보고 새 위젯을 수동 설치로 판정하고 있었다.

문제 해결

앱이 Pin을 요청할 때 같은 부팅 안에서만 유효한 2분짜리 표시를 남긴다. 그 사이에 처음 유효해진 위젯은 Pin 설치로 기록한다. 표시는 비교 후 지우는 방식이라 더 최근의 Pin 요청을 잘못 지우지 않고, 표시 저장에 실패해도 설치 흐름은 멈추지 않는다.

재발 방지

시나리오별 테스트 7개를 추가했다.

  • 런처 업데이트가 먼저 와도 Pin으로 한 번만 기록

  • Pin 콜백이 먼저 와도 Pin으로 기록

  • Pin 요청이 없으면 수동으로 기록

  • 오래된 표시와 다른 부팅의 표시는 무시

  • 더 최근의 Pin 요청 표시는 유지

  • Pin 뒤에 수동 설치 안내를 거치면 표시를 지움

문제 4. 새 장면을 추가하면 기존 집의 일정이 바뀐다

문제 발생

기존 Ambient 장면(고양이·새·택배)에 강아지와 화단 물 주기를 추가하려고 보니, 종류를 늘리는 순간 이미 있는 집의 장면 일정까지 다르게 뽑히는 문제가 있었다.

원인 식별

장면 추첨이 종류 목록의 순서와 개수에 의존한다. 목록이 바뀌면 같은 집, 같은 날이라도 다른 결과가 나온다. 한 번 정해진 일정은 다시 뽑지 않는다는 원칙(W-09)과 충돌한다.

문제 해결

일정에 버전을 두었다. v1은 기존 세 종류 그대로라 결과가 비트 단위로 같고, 새로 만드는 집만 다섯 종류인 v2를 쓴다.

  • 첫 강아지는 Day5에 확실히 나오고 1–7일 안에 한 번 더 온다. 첫 물 주기는 J3 완료 이틀 뒤에 나오고 3–7일 뒤 다시 나온다.

  • 물 주기 장면은 화단이 설치돼 있어야 그려진다. 사람은 그리지 않고 물뿌리개와 젖은 화단만 보여준다.

  • 앱의 Today's Home에는 “강아지가 놀러 왔어요”, “화단에 물을 줬어요”가 뜬다.

재발 방지

  • v1의 기존 테스트 벡터가 그대로 통과해야 한다. 여기에 v2 벡터, 버전별 종류 목록, 보장 규칙 테스트를 더했다.

  • 서버 쪽에는 이벤트 종류 허용 목록이 앱의 종류 코드와 정확히 일치하는지 검사하는 테스트를 추가했다. 앞으로 종류를 늘릴 때 서버만 빠뜨리면 테스트가 실패한다.

문제 5. 낮에만 어울리는 장면이 밤이나 비 오는 날에도 그려진다

문제 발생

정식 아트가 나오기 전에 기능을 실기기에서 확인하려고, Blender 스크립트로 다섯 종류 장면의 점토 느낌 임시 형상(placeholder)을 만들었다. 벤치에서 낮잠 자는 고양이, 우편함 위의 새, 문 앞 택배, 데크 위 강아지, 물뿌리개 옆 젖은 화단 같은 것들이다. 포즈마다 어울리는 시간과 날씨를 정해 두었는데, 맞는 포즈가 없는 상황에서도 장면이 그려질 수 있었다. 예를 들어 낮과 저녁용 포즈만 있는 새가 밤에 나타나는 식이다.

원인 식별

포즈를 고르는 규칙에 대체 경로가 있었다. 시간·날씨·필요 오브젝트가 모두 맞는 포즈가 없으면, 필요 오브젝트가 없는 '기본 포즈'를 시간과 날씨를 보지 않고 골랐다.

문제 해결

기본 포즈도 자기가 정한 시간과 날씨 안에서만 쓰이게 했다. 그릴 수 있는 포즈가 없으면 그 장면은 건너뛰고, 그날 활성화된 다음 장면을 그린다. 렌더 키와 계측 로그에도 실제로 그린 장면을 남긴다.

임시 형상은 색 레이어와 땅·벽·다른 오브젝트 위에 지는 그림자 레이어로 나눠 뽑았다. 기본 레이어 캐시는 건드리지 않아서 Ambient 작업 112건만 새로 렌더했고, WebP 384장이 추가됐다. 새벽에는 이 임시 형상들을 정식 에셋 구조(source.blend·asset.json·previews/·exports/)로 옮겨 6종을 에셋 카탈로그에 올렸다(아직 커밋 전).

재발 방지

  • 비 오는 날 맞는 포즈가 없으면 아무것도 그리지 않는지, 밤에는 낮 전용 새 대신 다음 장면(고양이)이 그려지는지 확인하는 테스트를 추가했다.

  • export 스크립트가 모든 포즈에 대해 겹침, 집의 정체성 부분 가림, Safe Zone 침범, 변형 누락을 자동으로 검사한다.

문제 6. 집 외관 장면으로는 날씨가 잘 보이지 않는다

문제 발생

지금까지는 집 외관을 꾸미는 방향이었다. 그런데 투명 배경 위젯에서는 흐린 날씨와 보통(중립) 날씨가 잘 구분되지 않는다는 위험(R-11)이 계속 남아 있었다. 선택형 보상도 후보가 하나뿐이라 실제로는 고를 게 없었다.

원인 식별

외관 장면은 투명한 배경 위에 집만 놓인다. 날씨를 직접 보여줄 하늘이나 배경이 없으니, 날씨 차이를 조명과 지면 변화만으로 전해야 했다.

문제 해결

오늘 가장 큰 결정이다. MVP는 집 외관 꾸미기 대신 방 한 칸을 예쁘게 꾸미는 데 집중한다.

  • 아이소메트릭 방 단면(벽 두 면과 바닥)에 창문을 내고, 창밖으로 하늘·비·눈·시간대를 직접 보여준다.

  • 이름 '한칸'과 그대로 맞고, 방 단면은 작은 위젯에서도 전체가 잘 읽힌다.

  • 30일 여정 구조, 벽돌 경제, Ambient 일정, 위젯 런타임과 합성기는 그대로 쓰고 보상만 가구로 바꾼다. 지금까지 만든 집 외관 에셋 60종은 보관해 두고 공개 확장 후보로 남긴다.

공교롭게도 오늘 마당용으로 만든 강아지와 물 주기 장면은 방 버전에서 러그 위 공놀이, 화분 옆 물뿌리개로 다시 그려야 한다. 그래도 일정 규칙과 코드는 그대로 남는다.

재발 방지

  • 날씨는 창밖 레이어로 직접 보여준다. 위치 기능이 꺼져 있으면 중립 하늘을 쓴다.

  • 선택형 보상(액자·생활 소품)은 처음부터 후보를 2–3개 둔다.

  • 다만 방의 구성, 10개 역할, Ambient의 방 버전은 아직 제안 단계다. 확정되면 PRD와 설계 문서에 반영한다.

문제가 될지 확인 중인 것: 고양이 꼬리 흔들기

이건 아직 문제가 아니라 실험이다. 한칸의 렌더 설계에는 '연속 애니메이션 없음'이라는 원칙이 있다. 그래도 RemoteViews의 ViewFlipper로 프레임을 넘기는 짧은 동작이 삼성 런처에서 실제로 움직이는지, 배터리와 CPU에 얼마나 부담인지 직접 확인해 보기로 했다.

  • 같은 장면에서 꼬리 각도만 다른 고양이 프레임 3장을 렌더했다.

  • 시험 위젯은 세 가지다. (가) 0.4초 간격으로 계속, (나) 탭했을 때만 3초, (다) 5초 간격으로 느리게.

  • 런처와 앱의 CPU 시간과 프레임 수를 조건별로 비교하는 adb 측정 스크립트도 만들었다.

지금은 시험 앱 빌드까지 마친 상태다. 실기기 관찰과 측정 결과를 보고 원칙을 바꿀지 사람이 정한다.

다음에 할 일

  • 방의 성격과 창밖 풍경, 10개 역할 확정하기

  • 설계 문서를 방 기준으로 고친 뒤 방 임시 아트 만들기

  • 갤럭시 S25에서 돌고 있는 P0 위젯 생존 측정 결과 정리하기