Document verified Android game architecture, rules and development procedures

This commit is contained in:
NINI
2026-10-07 03:15:08 +09:00
parent ea2ad4506c
commit 2d45e11120
30 changed files with 676 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# 시스템 구성
MagnetismHeroes는 기기 한 대에서 실행하는 오프라인 클라이언트다. Unity 장면 하나가 시작·선택·퍼즐·자유 놀이 화면과 입체 놀이터를 함께 유지한다. 서버나 원격 저장소를 거치지 않아 화면, 수학 상태, 로컬 저장의 일치가 가장 중요한 연결 경계다.
| 구성 | 책임과 연결 방향 |
|---|---|
| Core | Unity 참조 없는 C# 데이터·수학 규칙·진행 제어. 저장 인터페이스를 선언하며 입력·렌더링·파일 구현에는 의존하지 않는다. |
| UnityRuntime의 시작 연결 | 고정 스테이지를 C# 정의로 변환하고 파일 저장 구현을 진행 제어에 직접 전달한다. 화면과 입력의 수명을 연결한다. |
| 입력·카메라 | Input System 포인터·터치를 해석하고 UI 판정과 3D Raycast로 조작 대상을 결정한다. 미리보기와 확정 명령을 구분해 Core에 전달한다. |
| 화면·연출 | Presenter가 확정 상태를 URP의 구·연결 빛·얼굴·망토와 uGUI·TextMeshPro 화면으로 표현한다. 음성 플레이어는 재생 큐만 소유한다. |
| 고정 스테이지 | Inspector에서 제작한 ScriptableObject가 초기 구성·목표·배경 참조를 제공한다. 플레이 결과는 원본 자산으로 돌아가지 않는다. |
| 파일 저장 | Core의 인터페이스를 구현해 JSON 상태 전체를 검증·교체·복구한다. GameObject나 애니메이션을 저장하지 않는다. |
| Editor | 프로젝트 자산 제작과 Android 빌드를 담당한다. Editor 전용 어셈블리이며 런타임은 참조하지 않는다. |
| 제작 원본·도구 | Blender·로컬 Qwen3-TTS·이미지 생성으로 FBX·PNG·WAV를 제공한다. 도구·모델·캐시는 앱 실행의 의존성이 아니다. |
```mermaid
flowchart LR
I[터치·버튼] --> U[입력과 시작 연결]
U --> C[진행 제어와 수학 규칙]
C --> P[Presenter·UI·3D]
C --> S[저장 인터페이스]
S --> F[기기의 JSON 파일]
U --> V[앱에 포함된 음성 재생]
A[고정 스테이지 자산] --> U
```
3과 2인 친구를 붙이는 경우, 입력 계층이 접촉 후보와 이동량을 미리 보여준 뒤 놓는 순간 두 묶음 ID를 전달한다. 진행 제어가 규칙을 호출하고 확정 결과를 받아 목표를 판단한다. 성공이면 완료 기록·미완료 상태 제거·같은 보드의 자유 놀이 상태를 한 번에 저장한다. Presenter는 결과에서 대표 얼굴과 숫자 5를 다시 만든다. 콜라이더 접촉 수나 흔들리는 Transform이 정답을 결정하지 않는다.
카메라·선택·연출은 같은 수명을 갖지 않는다. 카메라 조작의 확정값은 저장으로 넘어가지만 분리 선택과 끌기 중간 위치는 입력 계층 안에 남는다. 연출은 상태에서 재생성하며, 화면 전환과 일시 정지에서 미리보기·지난 음성을 정리한다. 같은 보드의 성공 전이에서는 숫자 큐를 유지하므로 학습 발화 완료와 게임 성공은 서로 기다리지 않는다.
컴파일 방향은 UnityRuntime → Core, Editor → Core·UnityRuntime이다. 의존성은 시작 지점에서 직접 연결하고 화면·제스처는 명시적 상태로 표현한다. 이 규모에서는 범용 DI 컨테이너·전역 이벤트 버스·MVVM·ECS를 전제로 하지 않는다.
+48
View File
@@ -0,0 +1,48 @@
# 놀이와 학습 규칙
대상은 5~7세다. 구 하나가 1이라는 대응을 직접 찾기·모으기·누르기·붙이기·떼기로 익힌다. 더 큰 수나 특정 배열을 더 높은 등급의 영웅으로 취급하지 않는다. 현재 학습은 1~10의 계수와 수의 합성·분해이며, 분리 경험을 나눗셈 교육 완료로 해석하지 않는다.
## 자석과 영웅 팀
구의 정체성은 위치나 표정과 독립적이다. 합체·분리·이동 전후에 같은 구들이 보존되고 각 구는 정확히 한 친구에 속한다. 친구의 값은 소속 구 개수다. 합체는 두 친구의 합집합이며, 분리는 한 친구를 선택한 구와 나머지 구의 두 집합으로 나눈다.
N개에서 선택 수가 1~N-1일 때만 나눌 수 있다. 0개·전체·다른 친구의 구·중복 선택은 확정 상태를 바꾸지 않는다. 떨어진 양 끝 두 구를 선택해도 5는 항상 2와 3인 두 친구가 된다. 분리 후에는 양쪽이 모이도록 배치가 조정될 수 있지만 구가 늘거나 줄지 않는다. 다시 가까이 붙이기 전에는 분리 직후 재합체하지 않는다.
자석은 놓은 위치에 떠 있다. 한 손가락으로 친구 전체를 옮기고 빈 공간을 쓸어 시점을 회전하며 두 손가락으로 확대·축소한다. UI에서 시작한 입력은 세계를 움직이지 않는다. 끌기는 계수 탭으로 중복 처리하지 않고, 취소나 영역 밖 놓기는 마지막 확정 상태로 돌아간다.
아이가 만든 배열이 영웅의 몸이다. 합체 연출이 구를 큰 구 하나나 정해진 몸체로 바꾸지 않는다. 안정 상태의 친구마다 기존 구 표면의 대표 얼굴·얇은 마스크, 숫자, 리본형 자력 망토가 각각 하나다. 연결 빛·선택 강조·이미 센 표시를 구별하고 장식은 새 구나 입력 대상이 아니다. 연출은 기본 크기·구 정체성·계수 기록을 바꾸지 않는다.
## 퍼즐 판정
수 세기는 정확히 N개를 제공한다. 모든 구를 직접 끌어 수집하여 하나로 모은 뒤 각 구를 한 번씩 눌러야 성공한다. 1번도 끌기와 누르기가 모두 필요하다. 카메라 회전·보기·단순 탭은 수집이 아니다. 이미 센 구를 다시 눌러도 개수와 음성이 늘지 않는다. 세다가 나누면 한 묶음 조건을 잃어 계수가 중단되며, 다시 모으면 기존 계수 기록에서 계속한다.
구성 퍼즐은 모든 친구의 크기를 정렬한 다중집합과 목표를 비교한다. 목표 [2,2]에 [4]나 [2,1,1]은 성공이 아니다. 구 위치·몸 모양·특정 구 ID 조합·행동 순서·최소 횟수는 판정 조건이 아니다. 목표와 다르면 부드럽게 반응하고 계속 조작한다. 시간제한·목숨·실패 점수·오답 초기화는 없다. 미완료 퍼즐의 공급·반납은 허용하지 않는다.
| 번호 | 시작 → 목표 |
|---|---|
| 1~5 | 각각 N개의 1을 모아 N까지 계수 |
| 6~9 | [1,1]→[2], [2,1]→[3], [3,1]→[4], [2,3]→[5] |
| 10~13 | [2]→[1,1], [3]→[2,1], [4]→[2,2], [5]→[3,2] |
| 14~15 | [3,1]→[2,2], [4,1]→[3,2] |
| 16~20 | 각각 6~10개의 1을 모아 해당 수까지 계수 |
| 21~24 | [5,1]→[6], [3,4]→[7], [4,4]→[8], [5,5]→[10] |
| 25~28 | [6]→[5,1], [7]→[4,3], [9]→[5,4], [10]→[5,5] |
| 29~30 | [5,3]→[4,4], [6,4]→[5,5] |
배경은 학습 목표를 바꾸지 않는다. 사물 뒤·위의 자석은 다른 시점에서 발견하고 잡을 수 있어야 한다. 한 시점에 모든 구가 보이는 것은 성공 조건이 아니다.
## 진행·도움·자유 놀이
새 기록에서는 1번만 열린다. 성공하면 다음 번호를 열고 같은 배치·공간에서 자유 놀이로 전환한다. 자동으로 다음 퍼즐을 시작하지 않는다. 완료한 곳은 다시 풀 수 있고 재플레이를 중단해도 과거 완료와 해금이 취소되지 않는다. 30번 뒤에 31번을 제공하지 않는다.
6번 합체와 10번 분리 시범은 최초 지정 진입에 한 번 표시한다. 시범과 도움은 실제 구를 대신 움직이거나 완료를 만들지 않는다. 도움은 현재 가능한 수집 대상·미계수 구·합체 또는 분리의 첫 행동을 제안하며 사용 횟수로 불이익을 주지 않는다.
자유 놀이는 정답 없이 0~10개의 구를 다룬다. 공급은 구 하나씩, 반납은 선택한 친구 전체다. 일부만 반납하려면 먼저 나눈다. 열한 번째 공급은 상태를 바꾸지 않고, 빈 놀이판에서도 다시 꺼낼 수 있다.
## 이어하기와 소리
미완료 퍼즐은 번호별로 하나씩, 자유 놀이는 전체에서 최근 하나만 보관한다. 완료한 번호의 재플레이 미완료 상태와 완료 기록은 함께 존재할 수 있다. 성공한 퍼즐의 미완료 상태만 제거하고 새로운 자유 놀이가 이전 자유 놀이를 교체한다. 두 퍼즐과 자유 놀이가 서로 덮어쓰지 않는다.
마지막 확정 구 위치·소속·수집·계수·카메라와 본 시범 기록을 복원한다. 임시 선택·드래그·변신 중간 프레임은 복원하지 않는다. 재실행은 시작 화면으로 들어가 아이가 퍼즐 또는 자유 놀이를 선택하며 지난 계수·축하를 반복하지 않는다. 손상 저장을 부분적으로 읽지 않고 유효본 전체로 복구한다. 복구할 유효본도 없으면 알림과 초기 상태를 제공하고 손상 원본은 보존한다.
계수는 즉시 확정하고 숫자 발화는 순서대로 이어 재생한다. 안내가 숫자 발화를 끊거나 뒤로 밀지 않는다. 같은 보드에서 성공해도 마지막 숫자를 끝까지 들려주며 소리 때문에 성공·새 조작을 지연하지 않는다. 화면 이동·일시 정지에는 이전 발화를 정리한다. 숫자·강조·표정으로도 정보를 전달해 소리만으로 풀이를 요구하지 않는다.
+79
View File
@@ -0,0 +1,79 @@
# 파일과 명령행 계약
현재 게임에는 HTTP API·원격 인증·웹훅이 없다. 외부에서 사용하는 경계는 로컬 저장 JSON과 개발용 명령행 도구다. 경로 인수는 호출한 PC의 파일 경로이며 쉼표로 분리하는 음성 ID 이외에는 경로의 공백을 따옴표로 감싼다.
## 진행 저장 JSON 버전 1
UTF-8 JSON 객체이며 필드 이름은 대소문자를 구별한다. 아래 필수 필드는 모두 있어야 한다. 배열은 `null`을 허용하지 않으며 `latestFreePlay`만 `null`을 허용한다. 중복 JSON 속성·뒤에 붙은 추가 JSON·잘못된 타입·필수 필드 누락은 읽기 오류다. 알 수 없는 추가 필드는 현재 명시적으로 거부하지 않는다. 버전 1 이외의 자동 변환은 구현되어 있지 않다.
| 최상위 필드 | 타입·제약 |
|---|---|
| `schemaVersion` | 정수 1 |
| `completedStageIds` | 중복 없는 정수 배열, 각 값 1~30. 형식 검증이 연속 번호를 요구하지는 않는다. |
| `seenTutorialIds` | 중복 없는 문자열 배열, 값은 `merge` 또는 `split` |
| `puzzleStates` | 아래 퍼즐 객체 배열, 최대 30개, 번호 중복 없음 |
| `latestFreePlay` | 자유 놀이 객체 하나 또는 null |
퍼즐 객체는 `stageId`(정의와 일치하는 정수), `balls`, `groups`, `collectedBallIds`, `countedBallIds`, `camera`를 가진다. 구 개수는 해당 고정 스테이지 총량과 같아야 한다. 수집·계수는 문자열 ID 배열이며 각각 중복 없이 `countedBallIds ⊆ collectedBallIds ⊆ balls.id`다. 구성 퍼즐은 두 배열이 비어 있어야 한다. 완료 기록이 있는 번호의 재플레이 퍼즐도 저장할 수 있다.
자유 놀이 객체는 `sourceStageId`, `balls`, `groups`, `camera`만 갖는다. 수집·계수 필드는 없다. source 번호는 1~30이고 완료 목록에 있어야 한다. 자유 놀이 구는 0~10개다.
| 공통 객체 | 필드·제약 |
|---|---|
| ball | `id`: 비어 있거나 공백뿐이지 않은 고유 문자열. `position`: x/y/z 유한 숫자 객체 |
| group | `id`: 같은 상태 안에서 고유하고 비어 있지 않은 문자열. `ballIds`: 비어 있지 않은 문자열 배열 |
| camera | `target`: x/y/z 유한 숫자. `rotation`: x/y/z/w 유한 숫자. `distance`: 유한 양수 |
구 ID와 묶음 ID는 각 종류 안에서 유일해야 한다. 저장 검증은 ID의 특정 접두사·정규식·길이 상한을 요구하지 않는다. 묶음은 실제 구만 참조하고, 모든 구가 묶음 전체에서 정확히 한 번 등장해야 한다. 빈 보드는 빈 묶음 배열을 가진다. 회전은 quaternion이며 `abs(x²+y²+z²+w²-1) < 0.001`이다. 저장 거리에는 양수 제약을 적용하며 화면 카메라의 현재 3~15 범위와 구분한다.
```json
{
"schemaVersion": 1,
"completedStageIds": [],
"seenTutorialIds": [],
"puzzleStates": [],
"latestFreePlay": null
}
```
읽기는 유효 주 파일 전체 → 유효 `.bak` 전체 순서다. 둘 다 없으면 새 기록으로 성공하며, 주 파일만 무효이고 백업이 유효하면 복구 알림과 백업 상태를 반환한다. 둘 다 무효이면 오류와 초기 상태를 제공하고 손상 원본을 진단 사본으로 보존한다. `.tmp`는 복원 후보가 아니다. 쓰기는 전체 상태 검증 후 임시 파일 flush와 파일 교체로 수행하며 쓰기 실패는 화면에 알린다. 백업은 이전 확정 상태일 수 있으므로 최신 조작 보존을 보장하지 않는다.
## 검증 진입점
```powershell
pwsh -NoProfile -File tools/verify.ps1 -Phase EditMode -Milestone Final
```
`Phase`는 필수이며 `EditMode`, `PlayMode`, `AndroidBuild`, `AndroidSmoke` 중 하나다. `Milestone`은 `FirstSlice`, `Composition`, `Persistence`, `Content`, `Final` 중 하나로 기본값은 `Final`이다. 프로젝트 루트에서 실행한다.
| 단계 | 출력·입력 의존 |
|---|---|
| EditMode·PlayMode | `artifacts/verification/<Milestone>/<phase>.xml`, 로그, `<phase>-evidence.json`. 실제 테스트 결과를 검사한다. |
| AndroidBuild | `artifacts/android/<Milestone>/MagnetismHeroes.apk`, 빌드 로그·APK 메타데이터·SHA. Final은 표준 APK 사본도 만든다. |
| AndroidSmoke | 같은 소스에서 생성된 해당 Milestone APK와 빌드 기록을 요구한다. 실제 설치·입력·상태·음성 원문과 결과 JSON을 남긴다. |
모든 실제 검사와 소스 전후 일치가 통과하면 종료 0, 실패·보고서 누락·빈 테스트·dirty 소스·APK 불일치면 종료 1이다. 잘못된 인수는 PowerShell의 인수 오류와 비정상 종료다. 실패 JSON은 `status: failed`, `error`를 기록할 수 있으므로 파일 존재만으로 통과를 판단하지 않는다. Final Android 하위 검사는 정상 30개 진행과 별도로 경계·빠른 계수·콘텐츠 검사를 실행한다.
APK 음성 별도 검사 호출은 `tools/android/inspect-voice-payload.ps1 -ApkPath <APK> -EvidenceDir <결과폴더>`다. Python과 고정 UnityPy 1.25.4 QA 환경을 준비하고 결과의 `apk-voice-payload.json`에 실제 AudioClip 개수·승인 PCM 비교·APK SHA를 남긴다. 현재 채택 44개와 다르거나 읽을 수 없으면 비정상 종료하며 APK에 도구가 설치되는 것은 아니다.
PCM 비교는 `tools/android/compare-audio.ps1 -ReferencePath <기준WAV> -CapturedPath <녹음WAV> -OutputPath <JSON> -RequireFullSpeech`다. 입력은 PCM16 mono WAV이며 도구 내부 비교는 24kHz로 맞춘다. 출력은 상관·발화 시작·길이·RMS·기준/녹음 SHA·passed와 전체 발화 검사 결과다. 상관 0.95 이상, RMS 32 초과, 비무음 표본 100 초과 및 전체 발화 조건을 만족하면 0, 불일치면 1이다. 지원하지 않는 WAV·무음·입출력 오류는 비정상 종료한다. 전체 발화 인수를 생략한 부분 비교는 잘림 합격 증거로 사용하지 않는다.
## 음성 생성·후보 내보내기
```text
python tools/audio/generate_voice.py --catalog <JSON> --profile <JSON> [--ids id1,id2] [--regenerate]
python tools/audio/generate_voice.py --catalog <JSON> --profile <JSON> --validate-only --manifest <JSON> [--ids id1,id2]
python tools/audio/prepare_voice.py --catalog <JSON> --profile <JSON> --manifest <JSON> [--ids id1,id2] [--target <폴더>]
```
catalog는 `schemaVersion:1`, 비어 있지 않은 `clips` 배열이다. 각 clip은 고유한 `id`(`[a-z][a-z0-9_]*`), 한국어 전체 `text`, 비어 있지 않은 `usage`, 1~30 정수의 비어 있지 않은 `stageIds`, boolean `required`를 가진다. text에 숫자 기호를 넣지 않고 ‘여덟’ 같은 발화 문장을 쓴다. 선택 ID는 중복 없이 모두 catalog에 존재해야 한다.
profile은 `schemaVersion:1`, `modelId`, `modelRevision`, `language`, `speaker`, `instruct`, `device`, `dtype`, `attention`, 정수 `seed`, 객체 `generationParameters`다. 현재 생성 도구는 승인된 Qwen 1.7B CustomVoice·Korean·Sohee와 `cuda:0`을 요구한다. modelRevision은 40자 소문자 hex, dtype은 bfloat16/float16, attention은 sdpa/eager다. 실제 채택 프로필은 bfloat16·sdpa다.
생성은 catalog 옆 `generated/<runId>/`에 원본 WAV·입력 catalog/profile snapshot·manifest를 남긴다. manifest는 입력·대본·설정 해시, 원래 생성 회차, generated/reused/failed 결과, 파형의 실제 sample rate·채널·길이·해시와 환경을 기록한다. `--ids` 생략은 전체 catalog 생성이며 `--validate-only`는 모델을 로드하지 않고 전체 필수 또는 선택 범위를 검사한다. 입력·파일·해시·생성 오류가 없으면 종료 0, 있으면 오류와 종료 1이다. 일치 입력과 파일은 재사용하고 `--regenerate`는 새 후보 회차를 만든다.
prepare는 유효 manifest를 받아 44.1kHz mono PCM16의 `edited/<runId>/`와 selection JSON, target의 WAV를 만든다. 선택을 생략하면 필수 ID를 대상으로 한다. 기본 target은 게임의 Voice 폴더다. 기존 runtime 파일과 바이트가 다르면 기존 파일을 보존하며 오류 종료 1을 낸다. 숫자 발화 원본이 2초를 초과하거나 편집으로 clipping이 생기는 경우도 오류다. 후보 검사 성공은 종료 0이며 selection은 `draft/not_listened`다. 자동으로 사용자 승인이나 active-adoption을 갱신하지 않는다.
## 모델 내보내기
Blender의 `--background --python tools/models/build_playgrounds.py`는 저장소 루트에서 실행한다. 입력은 스크립트의 모델 정의이며 `SourceAssets/Models/PlaygroundProps.blend`와 Presentation/Models의 13개 FBX를 재작성한다. 사용자 인수를 받는 범용 모델 CLI가 아니다. Blender 실행·내보내기 오류는 비정상 종료하며 자동 성공 출력은 Unity의 방향·재질·콜라이더 검수를 의미하지 않는다.
+38
View File
@@ -0,0 +1,38 @@
# 구현과 검사에서 확인한 문제
## 입력 대상과 가려진 구
- 모바일 회전 검사에서 시작 화면으로 튀는 현상은 투영 좌표가 UI 영역에 들어가 버튼을 누른 경우였다. 구 위치와 카메라만 보고 탭하지 말고 현재 화면의 UI 안전 영역과 앞쪽 구를 먼저 확인한다. 화면 밖이면 실제 핀치·빈 공간 회전으로 시점을 바꾼 뒤 다시 투영한다.
- 마지막 구의 계수가 수십 번 실패해도 구 ID가 남아 있을 수 있다. 구름 아치 뒤의 구를 좌우 회전만 반복해 찾지 못한 실제 사례는 상하 회전 한 번으로 해결됐다. 시점 탐색에 두 회전 방향을 사용하고 변경 전후 같은 구·위치를 대조한다. 모든 구가 한 시점에 보여야 한다는 조건을 추가하면 자유로운 3D 배치와 충돌한다.
- 화면의 ‘나누기’와 ‘취소’ 버튼을 위치만으로 구분하면 분리 모드에 들어가지 않은 채 구를 누를 수 있다. 고정 AVD에서 확인한 실제 라벨과 캡처로 상태를 확인하고 일반 모드·선택 모드의 부정 대조군도 함께 확인한다. 이 픽셀 판정은 다른 해상도에 그대로 적용할 수 없다.
- 소품에 가려진 선택은 요청한 ID와 다른 구를 누를 수 있다. 이미 목표 2+3으로 완료된 실제 상태를 다시 합치려 하면 미완료 보드가 없어 실패한다. 현재 화면·전체 ID·목표 구성으로 실제 완료를 확인하고, 요청 ID와의 일치가 검증되지 않았다는 사실은 그대로 기록한다. 정확한 선택 경계 검사는 별도 구성이 필요하다.
## 저장 관찰과 PowerShell
- 숫자 마지막 입력 직후 파일을 읽으면 계수가 9인 이전 저장이 보이고 수십 ms 뒤 자유 놀이 상태가 보일 수 있다. 즉시 읽기 한 번으로 실패 판정하지 말고 짧은 한도 안에서 실제 저장을 반복 관찰하며 각 JSON과 UTC를 보존한다. 마지막 발화가 끝난 뒤까지 기다리면 ‘성공이 소리 완료를 기다리지 않는다’는 조건을 검증할 수 없다.
- 구 ID 하나를 PowerShell scalar 문자열로 받은 뒤 `[0]`을 적용하면 첫 문자만 전달된다. 네이티브 입력에 ID 배열이 필요한 경계에서는 `@(...)`로 감싸고 투영 전에 해당 ID가 실제 보드에 정확히 하나 존재하는지 확인한다.
- PowerShell AST 파싱 성공은 named argument나 실제 데이터의 의미를 보장하지 않는다. `Where-Object`의 연산자와 인수를 분리해 쓰고 동일 실제 상태로 호출까지 확인한다.
- 퍼즐 DTO에는 수집·계수 목록이 있지만 자유 놀이 DTO에는 없다. 빠른 계수 시험을 자유 놀이 저장에 계수 필드를 붙여 준비하면 퍼즐 준비가 되지 않는다. 실제 완료·수집 원문에서 출처를 남기고 별도의 퍼즐 상태를 준비한다. 이러한 주입은 정상 순차 진행 증거가 아니다.
## 음성과 APK 증거
- incremental Unity BuildReport의 packed-assets 목록이 비어 있어도 실제 APK에 음성이 들어 있을 수 있다. 이번 빌드의 직렬화 파일을 풀어 AudioClip과 실제 PCM을 확인한다. `.split0..N` 파일은 이어 붙여 읽고 다른 빌드의 UUID 결과를 재사용하지 않는다.
- 같은 제품 소스라도 다른 디렉터리에서 만든 APK의 SHA가 달랐다. 검사에 사용한 빌드·표준 사본·설치 후 가져온 APK는 해당 실행의 실제 해시로 연결한다. 이전 빌드 파일 이름이나 소스 동일성만으로 바이트 동일성을 주장하지 않는다.
- 짧은 파형 일부의 높은 상관은 전체 숫자가 잘리지 않았다는 증거가 아니다. 전체 발화 비교와 순서·중복·꼬리 무음을 함께 보고 앞/뒤/중간 잘림·중복·잡음·다른 발화 대조군이 실제 거부되는지 확인한다. PCM 검사도 발음에 대한 사용자 청취 승인을 대신하지 않는다.
- 녹음 완료 뒤 PID로 프로세스를 다시 찾으면 재사용된 브라우저 PID를 기다릴 수 있다. 준비 시 확인한 이름·시작 시각과 원래 process handle을 유지하고 자기 녹음 프로세스의 종료만 기다린다. 무관한 브라우저를 종료해 해결하지 않는다.
- JSON에서 UTC 시각이 이미 DateTime으로 읽혔는데 다시 지역 문자열로 변환·파싱하면 Kind가 사라질 수 있다. 원래 UTC 값을 직접 사용하고 녹음·성공 관찰의 시간 순서를 원문으로 확인한다.
- ADB daemon 재시작은 이전 파이프를 물려받아 wrapper를 붙잡을 수 있다. 분리된 숨김 프로세스로 실행하고 실제 device·boot 상태를 확인한 뒤 앱 검사를 시작한다. 오프라인 전환과 녹음 후에는 speaker·remote_submix·default 세 음량 및 네트워크 상태 모두를 원래 값으로 복원한다.
## Unity 자산
- 배치 Edit/Play 검사에서 URP의 Editor 생성 runtime 참조 목록이 비워지고 설정이 LF로 재작성됐다. 검사 전 Git 원문과 다른 모든 바이트가 같고 알려진 목록 변환만 존재하는지 확인한 경우에만 정리했다. 실제 설정 변경을 같은 처리로 숨기면 안 된다.
- Blender FBX의 root 회전을 제거하면 배경 소품이 눕는다. 제작 원본은 m·Z-up이고 내보내기는 -Z forward·Y-up이다. Unity import scale 1과 bakeAxisConversion을 맞추고 프리팹 wrapper에서 import root 회전을 유지한 뒤 바닥 피벗을 실제 화면에서 확인한다.
- 아치 전체를 BoxCollider로 감싸면 보이는 구멍도 Raycast를 막는다. 실제 아치 메시의 MeshCollider로 구멍을 보존하고 뒤쪽 자석을 선택할 수 있는지 확인한다.
- 투명 얼굴 PNG는 일부 뷰어에서 alpha 0 픽셀의 RGB가 파란 배경처럼 보인다. 원본 alpha와 실제 Unity 구 표면의 가장자리를 함께 확인한다. 현재 v2는 도구 편집 결과 자체를 채택했고 별도 래스터 변환 없이 런타임 파일과 같은 바이트를 유지한다.
- 글꼴 이름의 `FirstSlice`는 과거 이름이며 현재 전체 한국어를 포함한다. 이름만 보고 다시 만들면 승인된 화면과 자산 GUID가 달라질 수 있다. 실제 glyph와 static atlas를 검사하고 새 문구의 누락만 보완한다.
## 제작 도구의 수명
초기 자산 builder는 현재 완성된 씬·스테이지·얼굴·음성 참조를 재작성할 수 있다. `ProjectBootstrap.Prepare/ConnectApp`, `CompositionAssets.Build`, `PersistenceAssets.Build`, `ContentAssets.Build`, `PresentationAssets.Build`를 정상 프로젝트 열기·검사 전 준비 작업으로 실행하지 않는다. 변경 대상과 생성 diff를 먼저 확인하고 승인된 기존 자산을 보존할 수 있을 때만 사용한다.
로컬 Qwen 환경의 설치·import, GPU 연산, 모델 로딩, 실제 생성, 파일 검수, 청취 승인은 각각 다른 확인이었다. 이미 승인된 44개는 재생성할 이유가 없으며 같은 seed가 다른 환경에서 같은 WAV 바이트를 보장하지 않는다. 새 후보는 원본 이력과 입력 snapshot을 유지하고 승인 파일과 혼합하지 않는다.
+23
View File
@@ -1,5 +1,28 @@
# T8 최종 검증 기록
## 통합 dev의 최종 검사 (2026-10-07)
최신 완료 검사는 루트 `C:/git/MagnetismHeroes`, 고정 소스 `ea2ad4506ce2288f7777c0eb017833723679f487`에서 수행했다. 아래 작업 디렉터리의 이전 결과와 구분한다. 네 실제 명령의 외부 종료가 모두 0이고 실행 전후 tracked 변경과 소스 변경이 없다.
| 실제 명령 | 결과 | 외부 종료 |
|---|---|---|
| `pwsh -NoProfile -File tools/verify.ps1 -Phase EditMode -Milestone Final` | 100/100, 실패·건너뜀·미확정 0 | 0 |
| `pwsh -NoProfile -File tools/verify.ps1 -Phase PlayMode -Milestone Final` | 47/47, 실패·건너뜀·미확정 0 | 0 |
| `pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidBuild -Milestone Final` | release ARM64 IL2CPP APK 생성·검사 | 0 |
| `pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidSmoke -Milestone Final` | Final 226/226, 하위 네 검사 실제 종료 0 | 0 |
Smoke는 2026-10-06 17:46:54.093493~18:03:58.028723 UTC에 실행됐다. 새 저장·실제 입력의 Fresh30 151/151, FinalBoundary 37/37, RapidBoundary 39/39, ContentBoundary 69/69가 통과했다. 정상 1~30 진행에는 저장 push나 완료 기록 주입이 없다. 두 미완료 퍼즐·최근 자유 놀이 독립 복원과 부분 계수·카메라, 별도 명시 QA 상태의 빠른 계수·전체 숫자 발화·마지막 발화 보존, 취소·손상 복구·오프라인을 확인했다.
최신 [APK](../artifacts/android/MagnetismHeroes.apk)·Final 사본·이번 설치 후 가져온 APK의 SHA-256은 **`0D341465FD2DC4809DF8FA423490A9CFB3BA755220A8685479399CACBD9BDDE8`**다. 아래 이전 작업 APK `223F...`는 별도 보존본이다. 이번 실제 APK에서도 승인 44개 PCM, 원본/편집본/runtime 132개 해시와 고지 3개의 정확한 일치를 확인했다. 효과음·배경음악은 제외하며 한국어 음성은 유지한다.
실제 원문은 `artifacts/verification/Final`의 `*-outer.json`, phase 기록·XML·네 child report와 JSON/PNG/PCM이다. [고정 보존 목록](../artifacts/verification/T8/BaseEa2/manifest.json)은 2,527개 파일의 SHA를 대조한 사본이다. [독립 원문 확인](../artifacts/verification/T8/independent-base-evidence-ea2.json)은 실제 234개 조건과 종료 0을 기록한다. 이전 작업 원문 16,032개는 `Implementation5eb`, 실패한 첫 통합 검사와 진단 1,146개는 `BaseD9Failure`에 따로 보존했다.
첫 통합 검사 d9의 AndroidSmoke는 18번의 가려진 구를 검사 도구가 좌우 회전만 반복하여 실제 종료 1이었다. 같은 APK·같은 구 ID/위치를 상하 회전 한 번과 탭으로 성공시킨 실제 진단을 남겼다. ea2는 계수 검사에 주기적 상하 회전을 추가한 한 줄의 QA 수정이며 제품 규칙·자산·저장 스키마는 변경하지 않았다. 수정 후 통합본의 네 검사를 새로 모두 수행했다. 실패 결과를 통과 결과로 바꾸지 않았다.
이후 변경은 개발 지침·인수인계 문서와 Unity가 실제 import하여 생성한 문서 메타데이터 3개다. import는 실제 종료 0이며 기존 제품 tracked 파일의 변경은 없었다. 원문은 `artifacts/verification/T8/DocumentationImport`에 있다. 제품 파일 동일성은 별도 기록으로 확인한다. 실제 Android 기기 조작·성능·학습 효과, iOS·스토어 게시 검증은 수행하지 않았다.
## 이전 작업 디렉터리의 고정 검사 기록
2026-10-07 KST. 작업 디렉터리의 고정 소스 `5ebb86e2a82c6005f870460e8434d7ad99c0e99d`에서 Final 네 단계를 모두 실행했다. 검사 전후 tracked 변경은 없으며 아래 종료 코드는 실제 외부 명령의 값이다. 루트 통합·completion 네 단계와 최종 인수는 별도 단계다.
T7은 사용자가 명시한 첫 버전 범위에서 완료됐다. **효과음·배경음악은 제외하며 추가 음원 요청이나 자리표시자를 요구하지 않는다. 승인된 한국어 음성 44개는 유지한다.** T8에서는 게임 규칙·자산·저장 스키마를 변경하지 않았다. 기반 `7db006544cc471240e4342c4fa4affcb7c616d71`과 Assets/Packages/ProjectSettings/SourceAssets의 모든 tracked blob이 같다.
+15
View File
@@ -1,5 +1,20 @@
# MagnetismHeroes 개발 인수인계
## 최신 재개·최종 검사 상태 (2026-10-07)
사용자의 “효과음·배경음악 없이 진행하겠습니다. T7을 마무리하고 T8을 진행하세요.” 요청에 따라 개발을 재개해 T7을 마감하고 T8의 통합 최종 검사를 완료했다. **아래 과거 중단·음원 대기·T8 미착수 설명은 현재 지시가 아니다.** 한국어 승인 음성 44개는 유지하며 효과음·배경음악은 첫 버전 제외 상태다.
- 로컬 `dev`의 `ea2ad4506ce2288f7777c0eb017833723679f487`에서 Final EditMode 100/100, PlayMode 47/47, AndroidBuild, AndroidSmoke의 실제 외부 종료가 모두 0이며 전후 소스는 clean이었다.
- 새 저장에서 실제 입력으로 1~30을 모두 해결했고 완료 기록 주입은 없다. Final 226개, 하위 Fresh30 151·경계 37·빠른 계수 39·콘텐츠 69개가 통과했다. 두 미완료 퍼즐과 최근 자유 놀이의 부분 계수·카메라 포함 독립 복원도 확인했다.
- 최신 APK는 `artifacts/android/MagnetismHeroes.apk`와 Final 사본이며 SHA-256 `0D341465FD2DC4809DF8FA423490A9CFB3BA755220A8685479399CACBD9BDDE8`이다. 실제 설치본·승인 44개 PCM·132개 원본 대응 SHA·고지 3개를 대조했다.
- 원본은 `artifacts/verification/Final`, 고정 사본은 `artifacts/verification/T8/BaseEa2`의 2,527개 파일과 manifest다. root 독립 원문 확인 234개도 실제 종료 0이다. 작업 디렉터리의 5eb 검사·APK는 `Implementation5eb`에 16,032개, 첫 통합 d9 실패·상하 회전 진단은 `BaseD9Failure`에 1,146개 파일로 보존했다. 다른 APK·소스의 증거를 혼용하지 않는다.
- ea2는 18번의 소품 가림을 찾아 세는 검사에 상하 회전을 추가한 한 줄 QA 수정이다. 제품 코드·자산·저장 스키마는 T7 완료 상태와 같다. 이후 개발 지침·인수인계와 새 문서의 Unity 메타데이터 3개만 추가했다. 실제 Unity import는 종료 0, 기존 제품 tracked 변경은 없으며 원문은 `artifacts/verification/T8/DocumentationImport`다. Editor·녹음은 종료하고 AVD만 유지했으며 런타임 단독 실행 소유권은 해제됐다.
- 첫 버전 개발 지침은 루트 AGENTS.md/CLAUDE.md, docs의 규칙·구조·저장/CLI 계약·운영·실패 대응 및 모듈별 AGENTS.md에 정리했다. 최종 독립 검토와 사용자 최종 승인은 아직 남아 있으며 승인 전 기획 원본을 보관·삭제하지 않는다. 최종 승인 상태는 후속 기록을 확인한다.
- Unity에서 체험하려면 `Assets/MagnetismHeroes/Scenes/Main.unity`를 연다. 초기 builder를 재실행하면 완성 자산을 덮을 수 있으므로 일반 실행 준비에 사용하지 않는다. 검증/Editor/ADB/녹음 조작자는 하나로 유지한다.
- 실제 스마트폰·태블릿의 손가락 조작·가독성·성능·발열·배터리, 아이 대상 사용성·학습 효과, iOS·스토어 배포는 후속 범위다. 원격 push·게시·구매를 수행하지 않았다.
상세 통합 결과는 [final-verification.md](final-verification.md)이며 실행·검사 절차는 [operations.md](operations.md)다. 아래는 당시 원문을 보존한 이전 진행 기록이다.
작성일: 2026-10-05. 사용자가 “작업을 잠시 중단하겠습니다. docs/handoff.md 문서에 인수인계 문서를 작성해주세요.”라고 요청했다. **개발을 중단했다. 사용자가 다시 재개를 요청하기 전에는 개발·검사·독립 검토를 자동으로 계속하지 않는다.**
재개 기록: 같은 날 사용자가 이 문서를 읽고 작업을 계속하라고 요청했다. **중단 요청은 해제됐으며 T7의 미완료 Android 검사와 필요한 최소 수정을 재개한다.** 아래 중단 당시 상태와 실제 최신 Git·검사 기록을 구분한다. T1~T6를 다시 개발하지 않는다.
+59
View File
@@ -0,0 +1,59 @@
# 실행과 제작 절차
## 프로젝트 열기
Unity Hub에서 저장소 루트를 Unity `6000.6.3f1`로 연다. Android Build Support와 해당 Editor의 SDK·NDK·OpenJDK가 설치되어 있어야 한다. 프로젝트의 Package Manager가 고정 패키지를 복원한 뒤 `Assets/MagnetismHeroes/Scenes/Main.unity`를 열고 Game View를 가로 비율로 설정해 Play한다. 캐릭터를 마우스 왼쪽으로 끌고 빈 공간을 쓸어 회전하며 휠로 확대·축소한다. 분리는 친구 선택 → ‘나누기’ → 구 선택 → 선택한 구 끌기 순서다. 에디터 마우스 경험은 실제 두 손가락 검증과 구분한다.
빌드만 할 때 Blender·conda·TTS 모델은 필요 없다. 이미 저장된 FBX·PNG·WAV를 사용한다. 초기 자산을 통째로 만드는 Editor builder는 완성 프로젝트의 실행 준비 명령이 아니다.
## 명령행 검증
PowerShell 7, Python, Unity CLI와 Android Studio의 platform-tools·emulator가 필요하다. Unity CLI가 PATH에 없다면 아래처럼 현재 설치 위치를 추가한다. 다른 PC에서는 실제 설치 경로로 바꾼다.
```powershell
Set-Location -LiteralPath C:/git/MagnetismHeroes
$env:PATH = 'C:/Users/baram/AppData/Local/Unity/bin;' + $env:PATH
git status --short
unity --help
```
열린 Editor와 배치 명령이 같은 프로젝트를 동시에 사용하지 않게 Editor를 종료한다. tracked 변경이 없는 검증 소스를 준비한 다음 아래 네 명령을 순서대로 실행하며 매번 `$LASTEXITCODE`와 결과 파일을 확인한다. 앞 단계 실패를 건너뛰고 완료로 표시하지 않는다.
```powershell
pwsh -NoProfile -File tools/verify.ps1 -Phase EditMode -Milestone Final
pwsh -NoProfile -File tools/verify.ps1 -Phase PlayMode -Milestone Final
pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidBuild -Milestone Final
pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidSmoke -Milestone Final
```
AndroidSmoke 전에 `Medium_Phone_API_37.0` AVD를 Android Studio Device Manager에서 실행한다. 검사 대상은 `emulator-5554`, 가로 2400×1080이며 에뮬레이터 gRPC 입력은 localhost:8554를 사용한다. SDK의 `adb devices`와 boot 완료를 확인한다. 현재 Final 입력 도구 일부는 `$LOCALAPPDATA/Android/Sdk`를 직접 사용하므로 이 SDK에 AVD와 adb가 있어야 한다. 다른 경로·해상도·실기기에는 입력 도구 조정이 필요하다.
**전체 Android 검사는 시험 앱 데이터를 초기화하고 별도 경계 상태도 준비한다. 이용자 데이터가 있는 실기기에 그대로 실행하지 않는다.** 정상 30개 순차 진행은 새 저장으로 시작하고 주입을 하지 않는다. 빠른 계수 등의 주입 사례는 별도 폴더에 기록한다. 입력·녹음·설치 작성자를 하나로 유지한다.
결과는 `artifacts/verification/Final`의 XML·phase JSON·로그·화면·PCM에 남는다. 빌드는 `artifacts/android/Final/MagnetismHeroes.apk`와 `artifacts/android/MagnetismHeroes.apk`를 만든다. 실제 설치는 같은 검사에서 수행한다. 재실행 전 이전 증거를 별도 폴더와 해시 목록으로 보존해 다른 소스·APK 결과가 덮이지 않게 한다.
## 환경값과 저장
| 값 | 사용 범위 |
|---|---|
| `MAGNETISM_APK_PATH` | 빌드 진입점에 넘기는 APK 출력 경로. 검증 wrapper가 해당 단계의 경로를 지정한다. |
| `ANDROID_SDK_ROOT` | 이를 사용하는 Android 보조 도구의 SDK 선택. 미지정 시 LOCALAPPDATA 기본 SDK를 사용한다. Final의 일부 입력 도구는 기본 경로를 직접 사용한다. |
| 로컬 AVD gRPC 토큰 | 개발 입력 연결용. 토큰을 문서·로그·프로필에 넣지 않는다. |
게임 저장은 `Application.persistentDataPath/progress.json`, Android 시험 앱에서는 `/sdcard/Android/data/com.magnetismheroes.game/files/progress.json`이다. `.bak`은 유효 백업, `.tmp`는 교체 전 작업 파일이며 읽기 복원에서 사용하지 않는다. 손상 원본 진단은 `.damaged-*` 사본으로 남는다. 정상 플레이 재시작은 시작 화면에서 원하는 이어하기를 선택한다. 새 게임 데이터가 필요한 검사는 AVD의 시험 앱 데이터만 초기화한다.
현재 APK는 ARM64 IL2CPP·가로 화면·minSdk26의 로컬 체험판이다. AVD의 x86_64 환경에서는 arm64 번역을 사용한다. 스토어 제출용 서명·AAB·게시 절차는 수행하지 않았고 현재 APK를 게시 준비 완료로 취급하지 않는다.
## 새 자산 제작
모델을 바꿀 때만 Blender `5.2.2 LTS`에서 `tools/models/build_playgrounds.py`를 실행한다. 저장소 루트에서 Blender의 `--background --python tools/models/build_playgrounds.py`를 사용하면 `.blend`와 13개 FBX를 재작성한다. 자동 생성 전에 기존 소품 변경을 보존하고 실제 내보내기 diff·방향·바닥 피벗·콜라이더를 확인한다.
음성 제작은 conda `qwen3-tts`의 CUDA 환경과 고정 1.7B CustomVoice/Sohee를 사용한다. 이미 승인된 44개는 그대로 사용한다. 새 대본이 필요할 때만 선택 ID를 별도로 생성한다.
```powershell
conda run --no-capture-output -n qwen3-tts python tools/audio/generate_voice.py --catalog SourceAssets/Audio/Voice/voice-catalog.json --profile SourceAssets/Audio/Voice/qwen-profile.json --ids guide_collect
```
출력된 manifest 경로를 파일 검사에 전달하고 새 후보는 `artifacts` 아래 별도 target으로 내보낸다. 기존 runtime와 바이트가 다른 후보의 기본 경로 내보내기는 거부된다. 청취·형식 검수와 사용자 채택 전에는 승인본 교체나 재생성 완료 처리를 하지 않는다. 생성 원본은 실제 sample rate로, 게임 파일은 현재 44.1kHz mono PCM16으로 보존한다. GPU가 없으면 CPU·온라인 서비스로 자동 전환하지 않는다.
얼굴 후보는 투명 PNG와 프롬프트·채택 해시를 남기고 숫자·한국어는 TMP로 유지한다. 실제 Unity 구 표면과 Android 화면에서 가장자리·표정·계수 표시를 확인한다. 모델·얼굴·음성·글꼴 교체 때는 해당 파일의 출처·조건·필요 고지가 APK에 유지되는지도 확인한다.
+18
View File
@@ -0,0 +1,18 @@
# 데이터와 자산 보호
현재 앱은 계정·서버·로그인·원격 권한 체계가 없는 오프라인 단독 게임이다. 아이의 이름·연락처·위치·음성 녹음·광고 식별자를 수집하지 않는다. 플레이에 인증 정보를 요구하거나 네트워크·마이크 권한을 요청하지 않는다. 보호 대상은 완료 기록, 이어하기 배치, 승인된 제작 자산과 그 출처다.
| 주체 | 가능한 작업 | 조건과 금지 경계 |
|---|---|---|
| 기기 사용자 | 열린 퍼즐·완료한 곳·최근 자유 놀이 실행 | 현재 앱의 로컬 기록만 사용한다. 계정 간 공유나 원격 복구는 제공하지 않는다. |
| 앱의 저장 처리 | 상태 전체 검증·파일 교체·유효 백업 복구 | 무효 상태를 부분 적용하거나 정상 백업을 손상 원본으로 교체하지 않는다. |
| 개발·검사 도구 | 지정한 시험 앱 설치·입력·녹음·시험 저장 준비 | 개발용 AVD에서만 수행한다. 실제 이용자의 저장 초기화나 원격 업로드를 정상 실행 절차로 사용하지 않는다. |
| 자산 제작 담당 | 후보 생성·출처 기록·승인 파일 채택 | 청취되지 않은 음성을 승인 상태로 올리거나 승인 없는 유료 자산 구매·게시를 수행하지 않는다. |
저장은 `Application.persistentDataPath` 아래에 둔다. Android에서는 앱에 할당된 외부 files 영역이므로 암호화된 비밀 저장소나 변조 방지 장치로 설명하지 않는다. JSON 검증은 손상으로 수학 상태가 깨지는 것을 방지하는 경계이며 악의적인 기기 소유자에 대한 인증 체계가 아니다. 앱 데이터 삭제·제거·기기 손실에 대한 클라우드 복구도 현재 없다.
읽기 실패·백업 복구·쓰기 실패는 플레이 화면의 짧은 알림과 개발 로그로 남긴다. 손상 원본은 진단 사본으로 보존하고, 백업 복구가 최신 조작까지 보장한다고 주장하지 않는다. 정상 플레이 동작·아이 행동을 외부 분석 서비스로 보내지 않는다. 검사 로그와 화면·PCM은 개발 산출물이며 플레이 분석 수집이 아니다.
제작용 모델 다운로드와 QA 도구 다운로드는 개발 PC의 동작이다. 모델 가중치·conda 환경·API 키·캐시·녹음 서버를 APK에 넣지 않는다. 로컬 에뮬레이터 입력 연결의 토큰은 메모리에서만 사용하고 로그·소스·공유 산출물에 기록하지 않는다. 토큰이 노출되면 해당 개발 연결을 종료하고 새 연결로 교체한다. 앱에는 사용자 인증 토큰의 발급·갱신·폐기 절차가 없다.
Numberblocks는 표현 참고다. 해당 작품의 캐릭터·음원·이미지를 제품 파일로 가져오지 않는다. 자체 모델, 생성 얼굴, 음성, 글꼴은 채택 파일과 출처·조건·고지를 연결한다. 이미지 생성과 모델 라이선스를 결과물의 독점권 보장으로 해석하지 않으며, 도구가 제공하지 않은 모델 이름을 추측해 기록하지 않는다.
+31
View File
@@ -0,0 +1,31 @@
# 개발·검증 기준
## 변경과 책임
작업 전에 아이의 행동, 입력 전 상태, 확정 결과, 무효 조작과 복원 결과를 합격 사례로 정한다. 새로운 놀이 규칙·저장 범위·플랫폼 범위가 필요한 변경은 사용자와 먼저 맞춘다. 모호한 조건을 코드로 먼저 굳히지 않는다. 승인된 동작을 만족하는 최소 변경을 하고 관련 없는 코드 정리·폴더 개편·프레임워크 추가를 섞지 않는다.
Core 어셈블리는 `noEngineReferences: true`와 빈 외부 참조를 유지한다. UnityRuntime은 Core를 호출하고 Editor 전용 API를 참조하지 않는다. 저장 구현은 Core가 선언한 인터페이스로 연결한다. Presenter·연출의 Transform 변경을 수학 상태 갱신 경로로 사용하지 않는다. Inspector 자산은 실행 중 읽기 전용이고 진행 저장과 혼합하지 않는다.
수학·진행 조작은 명시적 호출과 거부 결과로 처리한다. 예상 가능한 무효 선택을 예외나 앱 종료로 바꾸지 않는다. 시작 지점에서 구현을 직접 전달하고 임의의 전역 서비스 탐색을 추가하지 않는다. 새 추상화는 실제 두 번째 사용과 필요한 경계가 있을 때만 도입한다.
타입·메서드·공개 속성은 PascalCase, 지역 변수는 camelCase, 비공개 필드는 `_camelCase`로 맞춘다. 저장의 lowerCamelCase 필드는 공개 JSON 계약이므로 단순 스타일 변경으로 이름을 바꾸지 않는다. 필수 Inspector 참조와 패키지·어셈블리·공통 씬은 한 작성자가 연결하고 누락을 실제 초기화로 확인한다.
## 소스·자산·환경
Unity 버전과 패키지는 프로젝트 설정·manifest·lock 파일에 고정한다. 변경 이유와 호환성 확인 없이 업그레이드하지 않는다. 소스·자산과 Unity가 생성한 `.meta`를 함께 관리하고 씬·프리팹·메타데이터는 Unity의 편집·생성 경로로 수정한다. 캐시·APK·모델 가중치·가상환경·검사 녹음은 소스 커밋에 넣지 않는다.
자산 교체는 원본·편집본·런타임 파일과 출처·검수의 대응을 보존한다. 승인된 음성의 다른 바이트를 같은 승인으로 취급하지 않는다. 후보 생성 성공이나 WAV 존재는 발음 검수 통과가 아니다. 숫자·한글은 텍스트로 표시하며 AI 그림 안의 숫자를 수량 표시의 근거로 쓰지 않는다.
로컬 개발 기반은 `dev`다. 커밋은 변경 이유와 결과를 설명하고 사용자의 다른 변경을 되돌리지 않는다. 로컬 구현 승인과 원격 push·스토어 게시·유료 구매 승인을 구분한다. Unity Editor, 배치 검사, AVD 입력·녹음은 동시에 여러 작성자가 조작하지 않는다.
## 합격 판정
수학 상태·저장·입력 충돌 변경에는 재현 가능한 정상·무효·경계·복원 검사를 둔다. 수정 전 실패와 수정 후 성공을 가능한 같은 사례로 확인한다. 색·문구 같은 낮은 영향의 변경마다 구현을 복제하는 테스트를 추가하지 않는다.
완성 APK 판정은 통합 소스에서 EditMode, PlayMode, AndroidBuild, AndroidSmoke를 모두 실행해 실제 종료 0을 확인한다. 테스트 수가 0이거나 필수 사례가 실패·건너뜀·미확정이면 합격이 아니다. 명령, 실행 시각, 검사 수, 로그, 소스 커밋·전후 변경 유무를 함께 기록하고 APK SHA와 실제 설치본을 연결한다. 한 단계의 빌드 성공을 실행·이어하기 검증으로 대체하지 않는다.
정상 순차 진행 증거에는 완료 기록을 주입하지 않는다. 경계 검사용 상태 주입은 출처와 바꾼 필드를 표시하고 정상 진행 결과와 구분한다. 파일 검사·PCM 비교·직접 청취·화면 확인은 각각 실제 관찰한 범위만 주장한다. 검사가 실행되지 못했으면 원인과 미검증 결과를 남긴다.
Unity가 자동 변경한 설정을 정리할 때는 Git blob 일치 또는 확인된 생성 변환의 정확한 일치가 증명된 파일만 복원한다. 임의의 설정 차이를 일괄 되돌려 소스 상태 검사를 통과시키지 않는다. 검사 후 제품 코드나 자산이 바뀌면 영향을 받은 검사와 최종 빌드를 갱신한다. 문서만 바뀐 경우 제품 파일 동일성을 확인하고 문서 검토를 수행한다.
AVD·화면 시뮬레이터 결과를 실제 스마트폰·태블릿의 손가락 조작, 성능·발열·배터리, 학습 효과 검증으로 확대하지 않는다. 실제 기기와 아이 대상 검증의 합격 기준은 그 환경에서 측정·관찰할 수 있는 사례로 별도 정한다.
@@ -0,0 +1,17 @@
# 규칙과 Unity의 분리
## 배경
자석의 수량·소속·중복 계수·분리 경계와 이어하기는 화면과 관계없이 정확해야 한다. 카툰 연출을 바꾸면서 수학 상태가 달라지지 않게 해야 하고, 초기 퍼즐 하나부터 Android까지 연결하며 확장한다.
## 결정
순수 C# 규칙·진행 제어와 Unity 입력·화면·파일 연결을 분리한다. 상태는 명시적 enum과 전이로 관리하고 Presenter·저장 인터페이스·시작 코드의 직접 연결을 사용한다. MonoBehaviour는 실제 장치·렌더링을 연결한다.
## 대안
MonoBehaviour 안에 규칙까지 모으면 첫 화면 구현은 빠르지만 규칙·저장 검사를 Unity 객체 수명에 묶는다. 범용 DI·이벤트·MVVM 프레임워크는 연결을 자동화하지만 최대 10구의 단독 게임에서 추적·디버깅 부담을 늘린다. 두 대안 대신 작은 직접 연결을 선택했다.
## 결과
연결 코드를 직접 작성하는 비용이 생기고 화면의 Transform이나 충돌 이벤트를 규칙 상태로 취급할 수 없다. 규칙 검사는 씬 없이 가능하며 장식·프리팹 변경을 수학 판정과 분리할 수 있다. 다만 입력과 실제 화면 연결은 별도 PlayMode·Android 검증이 필요하다.
@@ -0,0 +1,17 @@
# 고정 스테이지를 Inspector에서 제작
## 배경
스테이지는 초기 묶음·목표뿐 아니라 자석과 장식의 3D 배치·테마 참조를 가진다. 같은 규칙으로 30개를 실행하고 제작 배치를 플레이 결과가 덮어쓰지 않아야 한다.
## 결정
ScriptableObject를 Inspector에서 제작하고 시작할 때 순수 C# 정의로 복사한다. 플레이 중 구·소속·카메라는 별도 상태와 JSON으로 유지한다. 번호마다 씬이나 정답 로직을 복제하지 않는다.
## 대안
외부 JSON으로 고정 정의를 만들면 Unity 밖 편집은 쉬워지지만 3D 배치와 자산 참조를 별도로 관리해야 한다. 처음부터 모든 배치를 코드 builder로 관리하면 생성은 빠르지만 Inspector의 미술 조정을 재실행으로 잃을 수 있다. 현재 Unity 제작 흐름을 우선했다.
## 결과
Unity 자산 GUID·Inspector 참조와 정의 변환 검사를 함께 유지한다. 실행 중 원본 자산을 저장 공간으로 사용할 수 없다. 데이터가 유효해도 실제 발견·조작 가능성은 에디터와 APK에서 따로 확인해야 한다.
@@ -0,0 +1,17 @@
# 퍼즐별 로컬 기록과 최근 자유 놀이
## 배경
아이들이 여러 퍼즐을 오가고 완료한 곳도 다시 풀 수 있다. 중단한 배치까지 이어야 하며 두 퍼즐의 진행과 최근 자유 놀이가 서로 덮이지 않아야 한다. 계정 없이 오프라인으로 혼자 플레이한다.
## 결정
JSON 버전 1에서 번호별 미완료 상태, 감소하지 않는 완료 기록, 본 시범과 자유 놀이 한 상태를 보관한다. 저장 인터페이스로 파일 접근을 분리하고 전체 검증·임시 파일 교체·유효 백업 복구를 사용한다.
## 대안
모든 플레이를 한 슬롯에 저장하면 단순하지만 다른 퍼즐로 이동할 때 배치를 잃는다. 자유 놀이를 번호별 여러 슬롯에 보관하면 작품을 더 남길 수 있지만 사용자가 정한 ‘최근 하나’와 다른 보관·선택 기능이 필요하다. 계정·클라우드는 기기 간 복구를 제공하지만 오프라인 단독 체험판에 인증·서버·동기화를 추가한다.
## 결과
완료 번호의 재플레이 미완료 상태와 완료 기록이 동시에 존재할 수 있다. 자유 놀이 새 진입은 이전 하나를 교체한다. 기기 손실·앱 데이터 삭제 후 원격 복구는 불가능하다. 스키마 변경에는 기존 유효 저장의 처리 결정을 먼저 해야 하며 unsupported 버전을 최신 형식으로 조용히 읽을 수 없다.
@@ -0,0 +1,17 @@
# 로컬에서 미리 제작한 학습 음성
## 배경
숫자와 안내는 5~7세가 듣기 쉽고 문구 수정이 가능해야 한다. 사용자가 설치한 conda `qwen3-tts`와 GPU·1.7B CustomVoice·한국어 Sohee 구성을 선택했다. 앱은 네트워크 없이 동작해야 한다.
## 결정
제작 PC의 Qwen3-TTS로 WAV를 생성하고 대본·고정 모델 revision·프로필·원본·편집본·청취 승인 해시를 연결한다. 앱은 채택된 파일만 포함한다. 제작 성공과 청취 승인을 구분한다.
## 대안
직접 녹음은 따뜻한 말투를 제어할 수 있지만 문구마다 녹음 참여가 필요하다. CPU·0.6B는 환경 준비가 가볍지만 제작 시간과 말투 지시의 차이가 있다. 목소리 설계·복제는 독자성을 늘리지만 일관성 검수를 추가한다. 온라인 TTS·런타임 생성은 오프라인 동작과 파일 품질의 고정 인수에 맞지 않는다.
## 결과
GPU와 모델은 새 제작에 필요하지만 Unity 플레이·빌드에는 필요 없다. 실행 중 임의의 새 문장을 즉석 발화할 수 없고 새 안내는 후보 제작·청취·채택을 거쳐야 한다. 같은 seed도 바이트 재현이나 발음을 보장하지 않는다. 수정된 ‘여덟’과 나머지 승인 파일을 다시 생성하지 않고 정확한 채택본을 유지한다.
@@ -0,0 +1,17 @@
# Unity·Blender·AI 얼굴의 혼합 제작
## 배경
친구는 아이가 만든 구 배열을 유지하면서 표정·마스크·연결 빛·망토로 변신해야 한다. 배경은 세 테마로 구별되며 숫자는 실제 수량과 대응해야 한다. 사용자는 Unity+Blender와 실제 2D 자산의 AI 활용을 선택했다.
## 결정
Unity에서 공유 구·대표 얼굴 배치·숫자·효과·프리팹을 만들고 Blender에서 작은 배경 소품을 제작한다. AI 이미지의 실제 채택은 투명 얼굴 atlas이며 한국어·숫자는 TMP로 표시한다. 원본과 Unity 입력 파일을 분리하고 출처·생성 입력·수정·검수·사용 조건을 보존한다.
## 대안
Unity 기본 도형만 사용하면 제작 부담은 줄지만 배경 소품 표현이 제한된다. AI를 참고 시안에만 쓰고 실제 2D를 직접 제작하면 세밀한 통제는 쉬워지지만 사용자가 허용한 제작 속도 이점을 쓰지 못한다. 숫자마다 고정 몸체를 모델링하면 캐릭터는 뚜렷하지만 아이가 붙인 모양을 유지할 수 없다.
## 결과
FBX 방향·피벗·콜라이더와 PNG alpha·구 위의 표정 위치를 각각 검수해야 한다. 생성 이미지에 독점권을 주장하거나 도구가 공개하지 않은 모델 ID를 추측할 수 없다. 얼굴 장식은 별도 머리나 구가 아니며 숫자를 그림에 굳히지 않는다. 테마 소품·배치의 다양성은 학습 정답 조건을 늘리는 근거가 아니다.
+9
View File
@@ -0,0 +1,9 @@
# 설계 결정
| 결정 | 선택과 비용 |
|---|---|
| [0001 규칙과 Unity 분리](0001-rules-boundary.md) | 화면 없이 규칙을 검증하고 연결 코드를 직접 유지한다. |
| [0002 고정 스테이지 자산](0002-stage-authoring.md) | Inspector의 공간 편집을 사용하고 별도 플레이 JSON을 유지한다. |
| [0003 로컬 이어하기](0003-local-progress.md) | 퍼즐별 슬롯과 자유 놀이 하나를 저장하고 기기 간 복구는 제공하지 않는다. |
| [0004 미리 제작한 학습 음성](0004-prebaked-voice.md) | 수정 가능한 로컬 TTS를 쓰고 실제 파일 청취·채택을 거친다. |
| [0005 혼합 자산 제작](0005-hybrid-assets.md) | Unity 표현·Blender 소품·AI 얼굴을 결합하고 각 원본·검수를 유지한다. |
+3
View File
@@ -0,0 +1,3 @@
# 미해결 문제
2026-10-07 통합본의 규칙·화면 연결·저장·AVD 진행 검증 범위에서 재현되는 미해결 결함은 없다. 발견한 입력 검사·저장 관찰·녹음 수명 문제는 수정 후 실제 사례로 확인했다. 실기기 사용성·성능과 iOS는 아직 검사하지 않았으며 관찰된 결함으로 기록하지 않는다.
+24
View File
@@ -0,0 +1,24 @@
# 현재 구현과 검증 범위
2026-10-07. 한국어·Android·가로 화면·오프라인 체험판을 구현했다. 수 세기 10개와 합체·분리·혼합 20개, 순차 해금·재플레이·도움·시범·성공 후 자유 놀이가 연결됐다. 자유 놀이는 공급·묶음 반납·빈 보드와 10개 한도를 지원한다. 미완료 퍼즐별 저장과 최근 자유 놀이 하나, 손상 파일 복구를 구현했다.
Unity 자석 구·얼굴/마스크·연결 빛·망토, Blender 소품 13개, 정원·구름·별 작업실 세 테마, 실제 AI 생성 얼굴 6표정과 한국어 UI가 포함됐다. 로컬 Qwen3-TTS의 숫자·안내 44개는 사용자 청취 승인 파일을 채택했다. 효과음·배경음악은 사용자의 첫 버전 제외 결정에 따라 포함하지 않았으며 미완료 자산으로 간주하지 않는다.
## 확인된 결과
통합 `dev`의 `ea2ad4506ce2288f7777c0eb017833723679f487`에서 네 검증 명령의 실제 종료가 모두 0이었다. EditMode 100/100, PlayMode 47/47이며 실패·건너뜀·미확정이 없다. Android 최종 검사 226개와 하위 정상 진행 151개·경계 37개·빠른 계수 39개·콘텐츠 69개가 통과했다. 하위 수를 서로 독립적인 제품 기능 수로 합산하지 않는다.
새 저장에서 1~30을 실제 입력으로 순서대로 해결했으며 완료 기록을 미리 주입하지 않았다. 서로 다른 두 미완료 퍼즐과 최신 자유 놀이의 배치·부분 계수·카메라가 강제 종료 뒤 각각 복원됐다. 별도 경계 상태로 10개 제한·반납·재공급·빈 보드·취소·손상 복구·오프라인 전체 숫자 발화·마지막 발화 보존을 검사했다.
원본은 `artifacts/verification/Final`이며 고정 사본은 `artifacts/verification/T8/BaseEa2`에 2,527개 파일과 SHA 목록으로 보존했다. 별도 원본 대조 `artifacts/verification/T8/independent-base-evidence-ea2.json`의 234개 확인도 통과했다. 최종 APK는 `artifacts/android/MagnetismHeroes.apk`, SHA-256 `0D341465FD2DC4809DF8FA423490A9CFB3BA755220A8685479399CACBD9BDDE8`다. 실제 설치본과 같은 해시이며 승인 44개 PCM과 필요한 고지 3개의 정확한 포함을 확인했다.
검증 환경은 Unity 6000.6.3f1과 API37의 2400×1080 AVD다. 음성의 실제 재생과 대표 화면, Editor의 스마트폰·태블릿 비율 검증은 수행했지만 모든 기기·모든 시점의 가독성을 증명한 것은 아니다. 이전 검사 실패는 별도 증거로 남기고 통과 결과로 바꾸지 않았다.
## 남은 확인과 확장
1. 실제 Android 스마트폰·태블릿을 준비해 손가락 이동/선택/핀치, 작은 화면 가독성·안전 영역과 최대 구·소품 상태의 프레임·메모리·발열·배터리를 측정한다. AVD 결과는 이를 대신하지 않는다.
2. 5~7세의 실제 사용으로 도움 이해·중복 계수 표시·분리 선택 난도를 관찰하고 학습 목적에 맞춰 조정한다. 현재 학습 효과를 검증한 실험은 없다.
3. iPhone·iPad는 Mac/Xcode와 실제 기기를 확보한 뒤 별도 빌드·조작·저장 검증을 수행한다. 스토어 배포·서명·AAB 준비도 후속 범위다.
4. 생활 장치·소풍 열차 등 영웅 미션과 곱셈·나눗셈 확장은 검토 아이디어다. 정해진 장치 배치나 새 성공 조건을 현재 퍼즐에 이미 구현한 것으로 취급하지 않는다.
효과음·배경음악 추가는 후속 요청과 사용자 파일 선정으로 진행한다. 추가 언어, 계정·동기화, 여러 작품 보관함, 넓은 탐험 월드, 광고·결제는 현재 구현 범위가 아니며 도입 여부부터 별도로 결정한다. 현재 승인 범위의 실행을 막는 미해결 문제는 없다.