feat: verify first Android puzzle and preserve short touches

This commit is contained in:
NINI
2026-10-05 04:21:45 +09:00
18 changed files with 779 additions and 73 deletions
+44
View File
@@ -0,0 +1,44 @@
# 첫 퍼즐 Android 검증
Unity 6000.6.3f1의 실제 Android 빌드는 ARM64 IL2CPP, 개발 모드 꺼짐, `BuildOptions.None`, Main 씬 하나로 구성합니다. Unity 6.5부터 Android x86_64 플레이어가 제거되었습니다([공식 변경 안내](https://docs.unity.com/ja-jp/engine/6000.5/manual/upgrade-guides/upgrade-guide-unity65)). 현재 API 37 Google Play AVD는 기본 시스템 ABI가 x86_64이며 지원 목록에 arm64-v8a, 번역기에 libndk_translation.so를 광고합니다. APK/기기 지원 ABI의 교집합과 실제 프로세스 실행을 검사하며 x86 네이티브 실행·실기기 성능 합격을 뜻하지 않습니다. ARM 번역의 원리는 [Google 공식 안내](https://android-developers.googleblog.com/2020/03/run-arm-apps-on-android-emulator.html)에 설명되어 있습니다.
Android Editor 콜백은 오프라인 앱에 자동 추가된 INTERNET 권한을 생성 manifest에서 제거합니다. Performance Testing 패키지의 order 0 콜백이 release에도 자동 생성하는 정확히 두 시험 JSON을 최후 순서 콜백에서 AssetDatabase API로 제거하며, 생성된 Resources 폴더가 비었을 때만 제거합니다. 다른 Resources 자산을 삭제하지 않습니다. URP가 실제 빌드에서 생성한 직렬화·shader prefilter 필드만 보존하며 YAML을 손으로 편집하지 않습니다. 전역 UPM/Gradle/Bee 캐시는 유지하고 Android 도구의 `.utmp` 캐시는 제외 규칙으로 처리합니다.
전용 프로젝트의 절대 경로에서 다음 두 명령을 실행합니다. 같은 프로젝트에 Editor와 batch를 동시에 열지 않습니다.
```powershell
pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidBuild -Milestone FirstSlice
pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidSmoke -Milestone FirstSlice
```
첫 API 37 headless 기동의 GPU auto는 SwiftShader GLES 3.0/Lavapipe Vulkan 1.3을 선택했으며 즉시 찍은 화면은 비어 있었습니다. 최종 검증 AVD는 `-gpu host -no-window -no-snapshot-save -grpc-use-token`으로 실행하며, 실제 선택된 렌더러는 최종 SurfaceFlinger와 `android-device-evidence.json`에 기록합니다. 이전 재기동의 SwiftShader 로그와 현재 호스트 렌더러를 구분하며 GPU 성능 합격은 주장하지 않습니다. 처음 시작 직후 프로세스만 생존하는 시점과 UI가 렌더링된 시점을 구분하며, 전체 화면 안내가 뜨면 Android의 안내를 닫고 실제 화면을 다시 캡처합니다.
`tools/android/exercise-first-slice.ps1`은 담당자가 설치한 시험용 앱 데이터만 초기화한 뒤 위 표의 실제 ADB 조작, 앱 pause/force-stop/재실행, 앱 전용 JSON 읽기, 오프라인 상태와 에뮬레이터 음성을 수집합니다. `android-state-checks.json`의 기계 검사는 실제 수집 필요·빈 계수·동일 보드 성공·자유 이동의 위치 변화·JSON 복원·과거 음성 없는 복원·오프라인 성공 전이와 네트워크 상태를 검사합니다. 그림의 뜻은 별도 직접 검수입니다. airplane=1, wifi=0, mobile_data=0 및 `Active default network: none`을 확인한 시간에 세 발화를 기록합니다.
`compare-audio.ps1`은 승인 원본과 실제 PCM을 mono PCM16 24kHz로 맞추고 가장 강한 구간의 위치를 찾은 후 전체 발화의 정규화 상관을 평가합니다. 기준은 상관 ≥0.95, RMS >32, 비무음 샘플 >100입니다. 세 발화의 시작/끝이 차례로 분리되어야 합니다. APK 생성 출처는 원본 `androidbuild-evidence.json`과 `apk-metadata.json`을 유지하고, Smoke의 재검사는 `apk-inspection.json`에만 씁니다. 시작/종료 dirty=false와 소스 SHA·APK hash를 대조하므로 이전 APK를 후속 커밋 산출물로 다시 이름 붙일 수 없습니다. 잘못된 SHA/hash 검사 복사본은 실제 기기 조작 전 실패해야 합니다.
그 거부 동작은 실제 Build 뒤 `pwsh -NoProfile -File tools/android/test-provenance.ps1`로 검사합니다. 원본 근거를 손상시키지 않고 별도 복사본 두 개의 소스 SHA/APK hash만 바꿔 Smoke의 실제 종료 1 및 기기 명령 0개를 확인합니다. 음성은 raw WAV뿐 아니라 수집 당시 소스 SHA/dirty·시각 메타데이터도 최종 Build와 맞아야 합니다. 입력 수정 전 ADB 짧은 `input tap` 두 번은 구 탭을 놓쳤고 같은 좌표 200ms 접촉은 정상 반응했습니다. 수정 전 원시 자료는 `pre-fix/`에 보존하고 수정 후 최종 절차는 짧은 ADB tap으로 다시 확인합니다. 물리 기기의 실제 손가락 동작이나 성능 합격으로 확대하지 않습니다.
APK는 `artifacts/android/FirstSlice/MagnetismHeroes.apk`, 원본 경로를 기록한 Unity 실제 packed-assets 목록은 APK 옆 `.packed-assets.txt`입니다. 근거는 `artifacts/verification/FirstSlice/`에 보존합니다. Build/Smoke의 명령·실제 종료 코드·소스 SHA/dirty·APK SHA-256, aapt 실제 badging/ZIP entries, 기기 ABI/번역기/graphics, ADB 명령별 종료 코드·화면·관찰 결과를 별도로 기록합니다. 설치·프로세스 생존만으로 게임 검사를 통과시키지 않습니다.
`tools/android/observe.ps1`은 실제 ADB 조작 후 screencap을 가져오고 각 명령을 `<step>-commands.json`에 기록합니다. Unity uGUI 문자는 Android native hierarchy에 노출되지 않으므로 PNG를 직접 열어 한국어와 화면 의미를 검사합니다. `android-observations.json`에는 아래 번호별 절차·기대와 실제 결과·근거 파일을 작성하며 APK hash·소스 SHA·기기 serial이 맞아야 합니다. 미실행·빈 파일·관찰 누락은 Smoke 실패입니다. 원본 상태는 앱 전용 저장 JSON을 ADB로 읽을 수 있을 때 추가 검증하며 release APK에 진단 서버·단계 우회·테스트 제어 기능을 넣지 않습니다.
| 번호 / 관찰 ID | 실제 절차와 기대 결과 |
| --- | --- |
| 1 landscape-korean-assets | 시작→친구 찾기→1. 가로 화면에 한글·숫자·자석 구 하나·얼굴 하나·번호 하나·기본 3D 소품과 재질을 확인합니다. |
| 2 precollect-tap | 자석을 수집 전에 탭합니다. 미완료 반응이 나오고 센 친구 0/1이며 계속 조작할 수 있어야 합니다. |
| 3 direct-collection | 구를 한 손가락으로 직접 끌어 놓습니다. 위치가 바뀌고 안내가 세기 단계로 바뀌며 수집됨·계수 0입니다. |
| 4 camera-orbit | 구·UI를 피한 빈 곳을 쓸어 카메라를 회전합니다. 같은 구가 유지되고 뷰가 달라집니다. |
| 5 incomplete-restore | 수집·이동·카메라 변경 후 Home/back으로 pause→force-stop→재실행→친구 찾기→1. 위치·카메라·수집 상태가 유지되고 0/1입니다. N=1에서는 첫 계수 즉시 성공하므로 부분 양수 계수 복원은 후속 N≥2 범위입니다. |
| 6 count-success-same-board | 복원된 구를 한 번 탭합니다. 같은 보드 배치로 자유 놀이터에 전환되고 친구 1이며 숫자 1 음성이 끝까지 재생됩니다. |
| 7 free-move | 성공한 같은 보드에서 구를 다시 끌어 위치를 바꿉니다. 새 화면으로 옮기지 않고 자유 조작이 됩니다. |
| 8 free-restore | pause→force-stop→재실행→자유 놀이 이어하기. 성공 후 위치·카메라·친구 1이 복원되고 과거 계수 음성을 다시 읽지 않습니다. |
| 9 offline-collection-count | Wi-Fi와 mobile data를 끈 실제 상태를 기록한 뒤 친구 찾기→1을 다시 플레이합니다. 직접 끌기와 탭 성공, 세 음성 출력이 네트워크 없이 동작합니다. |
| 10 offline-restore | 오프라인 자유 놀이 배치를 바꾼 뒤 pause→force-stop→재실행→이어하기. 배치·카메라가 유지됩니다. |
| 11 guide-collect-playback | 1 진입 시 실제 에뮬레이터 PCM을 받아 비무음 구간과 승인 guide_collect 원본 파형을 대조합니다. |
| 12 guide-count-playback | 직접 수집 시 실제 PCM을 받아 승인 guide_count 원본과 대조합니다. |
| 13 count01-playback | 한 번 계수 성공 시 실제 PCM을 받아 승인 count_01 원본과 대조하며 자유 놀이 전환 뒤에도 마지막 읽기가 유지되는지 확인합니다. |
`tools/android/record-audio.ps1`은 localhost EmulatorController.streamAudio의 24kHz mono S16 PCM을 WAV로 기록합니다. SDK에 설치된 공식 `emulator_controller.proto`의 wire format을 사용하고 외부 패키지 설치를 하지 않습니다([공식 서비스 정의](https://android.googlesource.com/platform/tools/base/+/refs/heads/mirror-goog-studio-main/emulator/proto/emulator_controller.proto)). 관찰을 위해 AVD를 `-grpc-use-token`으로 시작하고 기본 localhost 바인딩을 확인합니다. 도구는 로컬 discovery 파일의 토큰을 메모리에서만 읽고 공개 근거에 인증키·비밀을 쓰지 않습니다. 패킷 수만으로 음성 재생을 통과시키지 않고 실제 비무음 신호·승인 WAV 대비 파형/시간을 평가합니다. 사람의 Android 출력 청취 검수와는 구분합니다.
승인·최종 음성 주장은 count_01·guide_collect·guide_count 세 파일만 대상으로 합니다. 그 밖의 로컬 숫자 파일은 미검수 초안이며 count_05는 런타임에서 제외되어 있습니다. 모델/conda/SourceAssets는 APK 제작 입력에 포함되지 않습니다. 효과음·BGM은 사용자가 나중에 추가하기로 했으며 아직 없습니다. 이 검증은 첫 퍼즐이며 30개 게임 완성, iOS, 배포, 실기기 성능 합격을 뜻하지 않습니다.