diff --git a/.dryforge/001/handoff.md b/.dryforge/001/handoff.md deleted file mode 100644 index 2ee58fc..0000000 --- a/.dryforge/001/handoff.md +++ /dev/null @@ -1,96 +0,0 @@ -# MagnetismHeroes 개발 인계 - -2026-10-06 최신 사용자 승인: 효과음·배경음악 없이 첫 버전을 진행하고 T7을 마무리한 뒤 T8을 수행한다. 기존 음원 선택 대기는 해제됐다. 아래 효과음·음악 포함·선정·반복/믹싱 기준은 후속 범위로 이동하며 한국어 음성 44개·시각 연출·오프라인 및 저장/규칙 기준은 유지한다. 기획 초기의 환경 상태 설명은 실제 최신 검증 기록을 대신하지 않는다. - -## 문서 역할과 우선순위 - -| 문서 | 역할 | -|---|---| -| `.dryforge/spec.md` | 게임 동작·범위·30개 스테이지·검증 기준의 기준 문서 | -| `.dryforge/plan.md` | 구현 작업 대상, 순서, 검증 방법을 제안하는 개발 계획 | -| `.dryforge/handoff.md` | 문서 사용법, 반드시 지킬 경계, 프로젝트 배경 | - -동작이 충돌하면 기획서가 우선한다. 기획서의 사용자 의도를 바꾸려면 사용자와 먼저 조정한다. 계획의 파일 배치와 작업 순서는 같은 기획을 실현하는 범위에서 수정할 수 있다. 기존 코드가 생겨도 구현 방식의 참고이며 게임의 목적을 대체하지 않는다. - -## 실행 개요 - -첫 개발 단계이며, 작업 폴더에 기존 게임 코드는 없다. 공통 프로젝트와 순수 C# 규칙을 준비한 뒤 1번 수 세기 퍼즐의 입력·화면·저장을 연결하고 Android에서 첫 실행을 확인한다. 이후 합체·분리 → 저장·자유 놀이 확장 → 30개 스테이지·도움 → 카툰·한국어 연출 → 최종 전체 검증으로 진행한다. 각 단계에서 플레이 가능한 이전 결과를 유지한다. 세부 작업과 의존 관계는 개발 계획을 따른다. 프로젝트 초기화와 단계별 실행 장면 연결은 실행 담당자가 맡고, 같은 장면을 여러 작업에서 동시에 수정하지 않는다. 첫 퍼즐부터 최소 자산의 포함·재생을 확인하고, 세 테마와 전체 배치를 연결한 뒤 최종 자산을 마감한다. - -현재 문서는 기획 결과다. Unity의 Android Build Support, SDK·NDK·OpenJDK와 Android Studio 및 가상 기기 설치는 확인했다. 게임 코드, 프로젝트 패키지 설치, APK 제작, 가상 기기 실행은 아직 수행하지 않았다. 기존 Git 이력과 `.gitignore`는 이 문서 작성에서 변경하지 않는다. 사용자가 문서를 확인한 뒤 이 대화에서 `dryforge:go`를 호출하여 개발을 시작한다. - -## 반드시 지킬 경계 - -1. 구 하나의 값은 1이고 합체·분리로 총량이 달라지지 않는다. 숫자 표시와 실제 개수가 일치해야 한다. -2. 모양이나 풀이 순서로 정답을 제한하지 않는다. 분리할 구들이 서로 떨어져 있어도 두 친구로 모은다. -3. 수 세기는 전부 찾고 직접 모은 뒤 각각 눌러 세야 한다. 이미 센 구를 중복으로 세지 않는다. -4. 기획서의 30개 스테이지와 순서를 그대로 구현한다. 더 적은 스테이지를 완성 결과로 제시하지 않는다. -5. 실패 점수·목숨·시간제한을 추가하지 않는다. 성공 후 같은 공간에서 자유롭게 놀게 한다. -6. 미완료 퍼즐은 스테이지별로 각각 저장하고, 최근 자유 놀이는 전체에서 하나만 저장한다. 완료 기록은 재플레이로 취소하지 않는다. -7. 한국어 소리와 자산을 포함한 오프라인 안드로이드 체험판이다. 서버·광고·결제·사용자 추적을 도입하지 않는다. -8. APK 생성과 Android 가상 기기 실행·진행·복원은 구분하여 검증한다. 가상 기기를 실행하지 못했으면 그 항목을 통과로 표시하지 않는다. -9. 실제 손가락 조작·실제 기기 가독성·성능은 기기 준비 후 검사한다. iOS 지원과 앱 스토어 배포는 이번 완료 범위가 아니다. -10. 기획을 바꾸지 않는 구현·튜닝은 개발자가 판단한다. 새로운 놀이 규칙·저장 범위·플랫폼 범위 변경은 사용자와 먼저 조정한다. -11. 순수 C# 규칙과 Unity 연결을 분리하고 URP·Input System·uGUI·TextMeshPro를 사용한다. 고정 ScriptableObject 자산과 JSON 플레이 저장을 혼용하지 않는다. -12. 첫 퍼즐부터 Android를 검증한다. 조기 검사나 에디터 검사만으로 최종 APK 검증을 통과 처리하지 않는다. 각 기능의 합격 사례와 실행 증거를 남긴다. -13. 합체 영웅은 개별 자석 구·대표 얼굴과 마스크·빛나는 연결부·자력 망토로 표현한다. 변신으로 구의 기본 크기나 아이가 만든 배치를 바꾸지 않으며, 분리 시 두 묶음으로 모으는 조정은 R04를 따른다. 장식이 구·숫자·선택·계수 표시와 입력을 가리지 않게 한다. -14. 배경 테마 3개를 제작한다. Unity·Blender를 함께 사용하고 실제 2D 자산에도 AI 이미지를 활용한다. 효과음·음악은 사용자 선택 절차를 따른다. 음성은 conda `qwen3-tts`의 로컬 Qwen3-TTS를 정비해 GPU·1.7B CustomVoice·한국어 Sohee로 제작한다. 임시·미선택 파일과 미검수 출력은 최종 완료가 아니다. -15. 숫자 음성은 계수 순서대로 들리고 같은 보드의 성공 후에도 마지막 숫자가 보존되어야 한다. 조작·성공 판정을 소리에 종속시키지 않는다. 화면 전환·일시 정지·복원 때 오래된 음성·연출이 남지 않게 한다. - -## 별도로 남길 의도 - -실행에 필요한 사용자 의도는 기획서와 계획에 담았다. 대화를 다시 읽어야만 알 수 있는 추가 규칙은 없다. 처음의 20개 안은 합체·분리 개념을 충분히 반복하기 위해 30개로 확장되었으며, 초기의 모든 모바일 플랫폼 동시 제작은 첫 단계 안드로이드 제작으로 좁혀졌다. 실행 중 이전 안을 다시 적용하지 않는다. - -## 프로젝트 배경 - -**실행 범위가 아닌 프로젝트 맥락이다.** 이 절은 개발 방향과 향후 프로젝트 안내 문서의 배경으로 사용한다. 구현할 항목과 완료 기준은 기획서에 따른다. - -### 1. 정체성 - -5~7세가 숫자를 직접 만져 보는 체험형 3D 퍼즐 게임이다. 자석 구 하나를 1로 삼고, 친구들을 합체·분리하며 수량과 숫자를 연결한다. 모바일 가로 화면에서 손가락으로 다루는 장난감 같은 경험을 지향한다. Numberblocks의 수량을 드러내는 캐릭터 표현이 참고이며, 구 자석을 직접 조작하는 것이 이 게임의 중심이다. - -캐릭터의 중심 문장은 ‘아이가 붙인 모양이 영웅의 몸이 되고, 연결되는 순간 대표 표정과 자력 망토가 완성된다’이다. 붙으면 하나의 영웅 팀, 나뉘면 함께 돕는 두 팀으로 표현한다. 변신을 보면서도 같은 구들이 보존된다는 사실을 따라갈 수 있게 한다. 특정 모양이나 더 큰 수를 상위 등급 영웅으로 취급하지 않는다. - -첫 체험판은 한국어·안드로이드·오프라인·혼자 플레이로 한정한다. 광고와 결제는 없다. 30개 스테이지로 수집·계수·합체·분리를 반복하고, 성공 후 같은 공간에서 자유롭게 놀게 한다. 사용자에게 중요한 것은 실제 수의 의미와 조작의 자유이며, 빠른 정답이나 실패 점수는 목표가 아니다. - -### 2. 놀이의 개념과 규칙 - -- **자석 구:** 값 1인 최소 단위다. 합체·분리·이동으로 소실되거나 복제되지 않는다. 자유 놀이에서만 꺼내거나 반납할 수 있다. -- **친구:** 하나 이상의 구로 된 묶음이다. 대표 얼굴 하나와 묶음 개수 표시를 가진다. 합체하면 한 친구가 되고, 분리하면 선택한 구와 남은 구가 각각 한 친구가 된다. 이전 묶음의 정체성보다 구성 구의 보존이 중요하다. -- **합체 영웅의 표현:** 기존 구 표면의 대표 얼굴·얇은 마스크, 접촉부의 빛, 묶음 뒤 리본형 망토로 구성한다. 안정 상태에서 얼굴·숫자·망토는 묶음마다 하나다. 합체 시 한 팀으로 통합되고 분리 시 각 팀에 생긴다. 짧은 포즈와 흔들림은 구의 정체성·기본 배치·계수 기록을 바꾸지 않으며 장식은 확정 상태에서 다시 만들 수 있다. -- **입체 놀이터:** 한 화면에서 회전·확대하며 둘러보는 작은 공간이다. 자석은 공중에 뜨고 모든 방향으로 붙일 수 있다. 탐험은 사물 뒤·위의 자석을 찾아 끌어오는 행동이며 보행 캐릭터나 넓은 월드는 없다. -- **배경 테마:** 서로 다른 세 장소의 외형을 작은 입체 세트와 주요 소품으로 구성한다. 소품은 공유할 수 있으며 테마와 배치는 고정 스테이지 정의에서 정한다. 테마가 학습 목표·정답 조건을 바꾸지 않고 성공 후 놀이와 복원에서도 유지된다. -- **학습 음성과 음원:** 화면의 계수·시범·도움에 대응하는 제작 파일이다. 숫자 발화는 순서를 지키며 입력·진행 상태를 소유하지 않는다. 효과음·음악의 최종 선택은 사용자가 한다. 사용자가 설치한 Qwen3-TTS 환경에서 실행 담당자가 대본을 파일로 생성·검수한다. 선택 전 임시 파일로 구현을 진행할 수 있지만 최종 품질·사용권 검수가 끝나야 완성으로 취급한다. -- **수 세기 퍼즐:** 목표만큼의 자석을 모두 모아 하나씩 눌러 센다. 단순히 목표 숫자를 표시하는 것만으로 끝나지 않는다. 구별 계수 기록은 배치가 바뀌거나 앱을 다시 열어도 유지된다. -- **구성 퍼즐:** 준비된 친구들을 목표 개수의 친구들로 바꾼다. 전체 묶음 구성만 같으면 모양과 풀이 순서는 자유다. 2와 2는 4 하나와 다르다. -- **나누기 선택:** 한 친구 안의 원하는 구들을 고른다. 서로 떨어진 구도 고를 수 있으며, 분리 후 양쪽을 각각 한 묶음으로 모은다. 0개나 전체 선택으로 빈 친구를 만들지 않는다. -- **도움:** 찾거나 셀 구 하나, 또는 목표에 접근할 첫 행동을 알려준다. 대신 실행하거나 사용 횟수로 불이익을 주지 않는다. -- **완료와 자유 놀이:** 성공하면 기록을 남기고 다음 번호를 열지만 화면을 강제로 바꾸지 않는다. 같은 친구들로 자유 놀이를 하며 최대 10개까지 꺼내거나 반납한다. 빈 놀이판도 다시 채울 수 있다. -- **퍼즐 저장:** 각 스테이지의 현재 미완료 상태를 따로 보존한다. 완료한 스테이지를 다시 풀다가 멈춘 상태와 과거 완료 기록은 함께 존재할 수 있다. -- **자유 놀이 저장:** 최근 하나만 보존한다. 퍼즐별 저장과 혼합하지 않는다. 새로운 자유 놀이로 넘어가면 이전 자유 놀이 저장을 교체한다. -- **학습 흐름:** 1~5 세기 5개 → 합치기 4개·분리하기 4개·혼합 2개 → 6~10 세기 5개 → 합치기 4개·분리하기 4개·혼합 2개. 스테이지는 순서대로 열리며 완료한 곳은 다시 플레이할 수 있다. - -### 3. 기술 방향 - -- Unity 6를 사용한다. 작업 환경에서 확인한 설치 버전은 `6000.6.3f1`이며 기존 게임 코드는 없다. 다른 버전으로 옮겨야 한다면 도구 호환성 이유와 영향을 먼저 확인한다. -- 클라이언트 하나에서 입력을 순서대로 처리하고 수학 상태를 확정한다. 온라인 공유 상태와 서버 API가 없으므로 분산 동기화는 필요하지 않다. -- 수량·합체·분리·계수·목표 판정과 진행 제어는 순수 C#으로 작성한다. Unity 객체는 입력·3D 배치·표현·파일 저장의 연결을 담당한다. 규칙을 화면 없이 검사할 수 있게 하며 렌더링·물리 효과 때문에 구의 개수가 바뀌지 않게 한다. -- URP·Input System·uGUI·TextMeshPro를 사용한다. 프리팹과 Inspector에서 카툰 캐릭터·화면을 제작한다. 큰 UI 문서 프레임워크보다 현재 게임의 Unity 제작 흐름을 우선한다. -- 명시적 상태 머신, Presenter, 저장 인터페이스와 시작 지점의 직접 의존성 연결을 사용한다. 범용 DI·이벤트·MVVM 프레임워크는 도입하지 않는다. 상태 머신은 화면·조작의 유효 전이를 드러내고 Presenter는 수학 규칙을 소유하지 않는다. -- 스테이지의 고정 초기 구성·목표·배치는 ScriptableObject로 제작하고 런타임 순수 C# 정의로 변환한다. 플레이 중 배치와 완료 기록은 원본 자산과 분리한다. -- 저장은 기기 내 앱 전용 파일의 JSON 버전 1로 처리한다. 퍼즐별 배치·수집·계수·카메라와 최근 자유 놀이를 복원한다. 저장 인터페이스는 파일 없이 진행 제어를 검사하기 위한 경계이며 사용자 계정·클라우드·여러 사용자 프로필은 없다. -- 한국어 소리와 화면 자산을 앱에 포함한다. 실행 중 네트워크, 마이크, 연락처, 위치 정보가 필요한 기능을 만들지 않는다. 자산의 출처와 사용 범위를 기록한다. -- Unity에서 자석 구·표현·효과를, Blender에서 입체 배경 소품을 만든다. AI 이미지도 실제 얼굴·UI 그림·텍스처에 사용하며 한글·숫자 표시는 TMP에서 처리한다. 제작 원본과 FBX·PNG·WAV 등 Unity 입력 파일을 분리하고 사용 조건·생성/수정·검수 기록을 남긴다. -- 음원 요청 목록과 Qwen3-TTS 대본·생성 프로필·파일 규격을 실행 담당자가 준비한다. 효과음·음악의 최종 선정은 사용자가 한다. conda `qwen3-tts` 환경을 재사용하고 GPU·`Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice`·`Korean`·`Sohee`로 로컬 생성한다. 제작 환경과 원본/생성 이력은 Unity 코드 밖에 두고 채택한 WAV만 게임에 연결한다. 모델 다운로드·음성 생성은 개발 실행 단계에서 수행한다. -- Qwen3-TTS 패키지와 클래스 import는 확인했으나 현재 PyTorch는 CPU 구성이다. 기존 환경을 무조건 재설치하지 않고 GPU 실행에 필요한 변경만 수행한 뒤 모델 로딩·실제 추론·청각 품질을 각각 확인한다. 개발 도구가 준비되지 않아 임시 파일로 진행한 상태와 최종 음성 완료를 구분한다. -- 작은 퍼즐을 입력부터 저장까지 완성하며 확장한다. Unity 에디터의 EditMode·PlayMode 검사를 기능마다 수행하고, 첫 퍼즐의 Android 설치·실행·복원을 확인한 뒤 전체 Android 검증을 마지막에 반복한다. 실제 기기 검증은 이번 결과와 분리한다. -- 요구사항에서 기능별 합격 사례를 먼저 정하고 정상·무효·경계·복원을 검사한다. 테스트 실행 수와 실패·미실행 여부, 소스 상태·APK 식별 정보·기기·로그를 남긴다. 테스트가 없거나 검사하지 못한 결과를 성공으로 처리하지 않는다. -- Core와 Unity 실행 코드, Editor 도구와 테스트의 참조 경계를 지킨다. C# 이름 규칙과 프리팹·씬의 단독 작성 규칙은 계획에 기록한다. 조작 임계값·흡착 거리·연출 시간은 플레이 감각을 보고 조정하는 값이다. 실제 기기 성능 합격은 기기 준비 후의 범위다. -- 파일 배치·직렬화 라이브러리·그림/음원 편집 도구·미술 조정값은 위 동작을 보존하는 구현 선택이다. 제작용 AI 이미지는 허용하되 새 게임 서버·런타임 AI·클라우드 TTS·승인 없는 유료 자산 구매·배포는 도입하지 않는다. - -### 4. 향후 범위 - -- 생활 속 문제를 돕는 영웅 세계관을 확장할 수 있다. 자석을 생활 장치에 연결하는 미션과 소풍 열차는 검토 아이디어이며, 장치 배치를 새로운 성공 조건으로 넣는 것은 별도 기획 대상이다. 이번 합체 영웅 외형 반영으로 R07의 30개 목표와 모양·위치에 무관한 판정을 바꾸지 않는다. -- 실제 안드로이드 스마트폰·태블릿에서 손가락 조작, 가독성, 성능 검증과 조정. -- 아이폰·아이패드용 빌드 및 Mac/Xcode·실제 기기를 통한 검증. -- 곱셈·나눗셈 등 수학 개념 확장. 이번 분리하기를 나눗셈 학습으로 간주하지 않는다. -- 영어, 광고·결제, 계정·동기화, 작품 여러 개 보관함, 자유 탐험 월드는 이번에 구현할 기능이 아니다. 향후 도입 여부도 별도 설계 대상이다. diff --git a/.dryforge/001/plan.md b/.dryforge/001/plan.md deleted file mode 100644 index ac9aa9e..0000000 --- a/.dryforge/001/plan.md +++ /dev/null @@ -1,626 +0,0 @@ -# MagnetismHeroes 개발 계획 - -2026-10-06 승인된 범위 변경: 첫 버전은 효과음·배경음악을 제외한다. 아래 음원 요청표·음악 반복/믹싱·효과음 인수는 향후 참고이며 T7/T8의 필수 완료 기준에서 제외한다. 한국어 숫자·안내 음성 44개, 시각 연출, 오프라인 재생·순차 발화·마지막 숫자·화면 전환/일시 정지 정리 검증은 유지한다. 기존 null 음원 연결은 범위 제외 상태로 인정하며 임시 음원을 새로 채택하지 않는다. T7은 보존된 실제 검증과 앱 blob 동일성·새 범위의 독립 검토로 마감하고, T8은 Final 네 단계와 전체 진행·독립 복원을 새로 검증한다. - -## 실행 방향과 현재 상태 - -한국어·오프라인·안드로이드 가로 화면의 30개 스테이지 체험판을 만든다. 수학 규칙은 순수 C#으로 구현하고 Unity는 입력·공간·표현·저장 연결을 담당한다. 작은 퍼즐 하나의 입력부터 저장까지 완성한 후 합체·분리와 전체 콘텐츠를 확장한다. 첫 퍼즐부터 Android를 검증하며, 마지막에는 완성된 APK로 전체 검증을 반복한다. - -현재 저장소에는 게임 코드·Unity 프로젝트 설정·자동 검사 명령이 없다. 아래 경로·클래스 역할·명령은 **개발하면서 만들 대상**이다. 이 문서 작성에서 테스트·APK 제작·가상 기기 실행을 완료한 것으로 해석하지 않는다. 기존 게임 규칙과 R07의 30개 스테이지 표는 유지한다. - -## 1. 아키텍처와 책임 경계 - -책임을 아래처럼 나누되 각 책임마다 별도 범용 프레임워크를 만들지 않는다. - -| 책임 | 소유하는 내용 | 하지 않는 일 | -|---|---|---| -| Core의 수학 규칙 | 구 ID, 묶음 소속, 합체·분리, 계수 기록, 목표 다중집합 판정, 공급 한도 | GameObject·Transform·UI·파일 접근 | -| Core의 진행 제어 | 스테이지 시작·복원, 유효한 조작 실행, 정답·완료 전이, 자유 놀이, 저장 요청 | 입력 장치 읽기, 얼굴·애니메이션 구현 | -| Unity 입력·공간 연결 | 터치 해석, Raycast, 끌기 미리보기, 카메라, 흡착 후보, 확정 조작 전달 | 접촉 프레임을 근거로 구 복제·삭제·정답 처리 | -| Unity 화면·소리 표현 | Presenter, 캐릭터·숫자·선택 강조, 메뉴, 안내, 효과음 | 독립적인 수량·완료 상태 보유 | -| Unity 데이터·저장 연결 | ScriptableObject 정의 변환, JSON 직렬화·파일 교체·복구 | 실행 중 고정 자산 수정, UI 객체 직렬화 | -| 시작 지점 | 규칙·진행·입력·표현·저장 구현의 직접 연결과 수명 관리 | 여러 곳에서 찾는 전역 서비스 등록 | - -컴파일 참조는 `UnityRuntime → Core` 방향이다. Core는 UnityRuntime과 UnityEngine을 참조하지 않는다. 저장 구현은 Core에서 선언한 저장 경계를 구현한다. 시작 지점이 그 구현을 진행 제어에 전달한다. Editor 코드만 자산 제작·검사·빌드 API에 접근하며 런타임에서 Editor 코드를 참조하지 않는다. - -초기 실행 어셈블리는 Core와 UnityRuntime 두 개로 충분하다. Core 안에서 수학 규칙과 진행 제어를 폴더로 구분한다. Editor와 EditMode·PlayMode 테스트는 별도 어셈블리로 분리한다. 파일마다 인터페이스나 어셈블리를 만들지 않는다. - -### 상태가 바뀌는 한 경로 - -```mermaid -flowchart LR - I["터치·버튼"] --> A["Unity 입력 해석"] - A --> C["진행 제어"] - C --> D["순수 C# 규칙 검증·상태 확정"] - D --> C - C --> P["Presenter·3D 표현"] - C --> S["저장 경계"] - S --> J["앱 전용 JSON 파일"] -``` - -예를 들어 3과 2를 붙이면 입력 연결이 두 묶음 ID를 전달하고, 규칙이 서로 다른 유효한 묶음인지 확인한 뒤 구 5개를 한 묶음으로 확정한다. 진행 제어는 목표 판정과 저장 요청을 처리하고 Presenter는 숫자 5와 얼굴 하나를 표시한다. 애니메이션 중간의 Transform이나 콜라이더 개수로 수량을 다시 판정하지 않는다. - -끌기 중 위치·분리 선택·연출은 미리보기다. 저장과 정답 판정은 확정 상태를 기준으로 한다. 조작 취소나 핀치 전환은 R03의 마지막 확정 상태로 복구한다. 앱 일시 정지에서도 연출 중간값 대신 이 확정 상태를 저장한다. 저장 실패를 파일 저장 성공으로 보고하지 않으며 복구 경로는 R12를 따른다. - -## 2. 적용할 디자인 패턴 - -| 방식 | 적용 지점과 목적 | 적용 범위 | -|---|---|---| -| 명시적 상태 머신 | 시작·선택·퍼즐·자유 놀이의 유효 전이와 일반/나누기 조작 구분 | 상태와 전이를 표·코드로 드러낸다. 상태마다 클래스를 만드는 것은 필수가 아니다. | -| Presenter | 상태를 숫자·얼굴·버튼·안내에 반영하고 UI 의도를 진행 제어로 전달 | UI에 수학 규칙을 넣지 않는다. 상태 변경을 구독한다면 해제도 같은 수명에서 처리한다. | -| 저장 인터페이스 | 진행 제어 검사에서는 메모리 저장 대역을, 실제 앱에서는 JSON 파일을 사용 | 파일 접근 경계 하나를 둔다. 범용 Repository 계층을 추가하지 않는다. | -| 직접 의존성 주입 | 시작 코드가 필요한 구현을 만들어 전달 | 외부 DI 컨테이너·전역 Singleton 서비스 탐색을 도입하지 않는다. | -| 데이터로 정의하는 스테이지 | 같은 규칙으로 30개 자산의 초기 구성·목표·배치를 실행 | 스테이지마다 별도 게임 로직을 작성하지 않는다. | - -합체·분리·계수·공급·반납은 명시적인 조작 호출로 표현한다. 취소는 미리보기 취소이며 범용 실행 취소/재실행 시스템을 뜻하지 않는다. 호출 결과를 따라 상태와 화면을 갱신하므로 전역 이벤트 버스도 필요하지 않다. 구가 최대 10개인 이 프로젝트에서는 ECS나 대규모 객체 풀 도입보다 규칙과 화면의 일치를 우선한다. - -## 3. Unity 제작 방식과 프로젝트 준비 - -프로젝트 초기화는 작업 번호 없이 실행 담당자가 수행한다. - -- 저장소 루트에 설치된 Unity `6000.6.3f1`로 URP 프로젝트를 준비한다. Android Build Support와 Unity 제공 SDK·NDK·OpenJDK 설치는 확인되었으므로 설치를 반복하지 않는다. External Tools에서 실제 사용 경로를 확인한다. -- Input System·uGUI·TextMeshPro·Unity Test Framework의 에디터 호환 버전을 선택하고 `Packages/manifest.json`, 잠금 파일과 `ProjectSettings/`에 고정한다. 개발 도중 패키지나 에디터 버전을 임의로 올리지 않는다. -- `Assets/MagnetismHeroes/Core/`에 순수 데이터·수학 규칙·진행 제어·저장 경계를 둔다. `UnityRuntime/` 아래 `Interaction`, `Presentation`, `StageAssets`, `Persistence`, `Bootstrap`으로 Unity 책임을 구분한다. -- `Editor/`, `Tests/EditMode/`, `Tests/PlayMode/`, `Scenes/`를 별도로 둔다. 초기 어셈블리·공통 폴더 메타데이터·검증 진입점은 공통 준비에서 한 번 만든다. Core의 Unity 참조 금지는 어셈블리 설정으로 검증한다. -- 시작/선택 화면과 플레이 공간은 소수의 씬으로 관리한다. 친구·숫자·목표 패널·놀이터를 재사용 프리팹으로 만들며 30개 번호마다 씬을 복제하지 않는다. -- 코드는 외부 C# 편집기에서 작성하고 씬·프리팹·배치·색·조명·애니메이션 연결은 Unity 에디터에서 제작한다. Unity 자산을 수정할 때는 에디터가 정상적으로 읽는 생성·수정 경로를 사용하고 참조 누락을 확인한다. -- 실행 담당자가 단계마다 공통 씬·시작 연결·빌드 포함 목록을 갱신한다. 기능 작성자는 전용 코드·프리팹·자산을 제공하고 같은 공통 씬을 동시에 수정하지 않는다. -- Android Studio의 `Medium_Phone_API_37.0` AVD는 생성되어 있지만 실행 검증은 하지 않았다. 사용 가능한 ABI·그래픽 환경을 확인한다. Unity 빌드용 SDK와 Studio의 가상 기기용 SDK 경로를 혼동하지 않는다. - -기본 그림은 단순 구와 임시 얼굴로 먼저 조작을 검증한다. 카툰 재질·세 테마 배경·표정·한국어 음성은 단계별로 확장하고 최종 파일로 교체한다. 아래 제작 절차와 음원·대본 요청을 첫 퍼즐부터 시작하여 전체 제작 단계까지 기다리지 않는다. 임시 자산으로 플레이가 된다는 사실과 R11·R14의 표현·소리 완성은 별개로 기록한다. 자산은 앱에 포함하며 런타임 다운로드를 요구하지 않는다. - -### 합체 영웅의 제작 방향 - -**아이가 붙인 모양이 영웅의 몸이 되고, 연결되는 순간 대표 표정과 자력 망토가 완성된다.** R11의 외형과 변신 순서를 Presentation에서 구현한다. 자석 구, 대표 얼굴·얇은 마스크, 접촉부의 빛, 묶음 뒤의 리본형 자력 망토, 숫자 표시를 재사용 가능한 표현 요소로 준비한다. 묶음 소속과 배치에서 표시를 구성하며 숫자마다 고정된 몸체 프리팹으로 교체하지 않는다. - -| 소개용 구성 | 자세·움직임의 예시 | -|---|---| -| 1인 친구 | 통통 움직이며 주변을 살피고 짧은 망토가 따라옴 | -| 2인 친구 | 붙은 두 구가 함께 기울며 균형을 잡음 | -| 3인 친구 | 삼각형은 안정적인 자세, 일렬 배치는 유연하게 움직이는 자세 | -| 5인 친구 | 둥근 묶음은 몸을 활짝 펴는 인상, 긴 묶음은 물결치는 움직임 | - -위 형태는 소개·외형 검토용 예시다. 플레이 중 아이의 배치를 예시 형태로 바꾸지 않으며 6~10도 같은 표현 원칙으로 조합한다. 시안의 색과 마스크 윤곽은 고정하지 않고 개별 구·숫자·선택·계수 표시의 식별을 우선해 조정한다. 게임 안에서는 모든 구의 기본 크기를 같게 유지한다. - -합체는 접근 미리보기 → 접촉 빛과 효과음 → 대표 얼굴·숫자·망토 통합 → 짧은 영웅 포즈로 표현한다. 분리는 선택 테두리·망토 흐름 → 끌어내기 확정 → 두 묶음 각각의 얼굴·숫자·망토 → 서로 바라보는 짧은 반응으로 표현한다. 선택만 한 상태, 0개·전체 선택, 취소에서 성공적인 분리 연출을 실행하지 않는다. - -Presenter는 확정된 묶음 변화로 표현을 갱신한다. 흔들림·망토·접촉 빛은 수학 상태와 저장 좌표를 수정하지 않고 입력을 가로채지 않는다. 연속 조작·화면 전환·복원 시 지난 연출을 정리해 현재 묶음과 맞춘다. 기존 상태로 표현을 재구성하므로 외형만을 위한 저장 필드를 추가하지 않는다. - -T2에서 기본 구·얼굴·숫자를 연결하고 T4에서 합체·분리 결과와 표현 전환 지점을 마련한다. T7에서 마스크·연결부·망토·표정·효과음을 완성하고 T8에서 최종 Android 화면과 반복 조작을 확인한다. - -### 자산 제작·전달·검수 - -Unity에서 자석 구·표현 프리팹·재질·특수 효과를 만들고 Blender에서 입체 배경 소품을 제작한다. AI 이미지는 시안뿐 아니라 실제 얼굴·UI 그림·텍스처에도 사용한다. 효과음·음악의 선택은 사용자가 맡는다. 음성은 이미 설치한 conda `qwen3-tts` 환경의 Qwen3-TTS를 정비해 실행 담당자가 생성·검수한다. 필요한 자산을 요청하고 전달받거나 선택된 파일의 사용권·형식·품질을 확인해 게임에 연결한다. 사용자 선택을 대신해 무료 음원·직접 작곡·AI 음악으로 결정하지 않는다. - -아래 수량은 **초기 제작 규모이며 조정 가능**하다. 같은 파일을 여러 용도에서 재사용할 수 있으나 필수 동작의 표현을 생략하지 않는다. 배경 테마 3개와 숫자 읽기 1~10은 기획서의 범위다. - -| 자산 | 첫 퍼즐에서 준비 | 전체 버전 제작 목록 | 제작·연결 방식 | -|---|---|---|---| -| 자석 친구 | 구 1종, 대표 얼굴, 숫자 표시 | 공유 구 메시·재질, 얇은 마스크, 표정 6종(기본·관심·집중·웃음·격려·축하) | Unity 기본 구 또는 공유 메시, 얼굴 PNG/간단한 표면 메시, TMP 숫자; 10개 고정 몸체를 만들지 않음 | -| 배경 | 단순 입체 바닥·가림 소품으로 조작 검사 | 서로 다른 테마 3개, 공통 소품 4~6종과 테마별 주요 소품 3~4종 | Blender 모델 → FBX → URP 재질 → Unity 프리팹; 같은 테마 안에서도 배치를 바꿔 사용 | -| 화면 | 목표·나누기·도움·진행 버튼의 기본 표시 | 시작·스테이지 선택·플레이·자유 놀이에 필요한 아이콘과 패널 | AI 시안/이미지 정리 → 투명 PNG; uGUI 연결, 한글·숫자는 TMP | -| 글꼴 | 기본 한글·숫자 표시 | 실제 화면 문구·숫자·기호를 포함하는 글꼴과 TMP 폰트 자산 | 사용 조건이 확인된 한글 글꼴을 가져와 필요한 글리프를 구성; AI 그림으로 글자를 대체하지 않음 | -| 특수 효과 | 수집·계수 강조 | 흡착 후보, 연결 빛, 분리 선택/확정, 계수 표시, 도움 강조, 성공 효과와 자력 망토 | 공유 고리/리본 메시·재질, 작은 Particle System; 상태별 강조는 구별 | -| 효과음 | 아래 요청표의 첫 퍼즐 필수 음원 | 합체·분리·계수·격려·성공·화면·자유 놀이의 소리 | 사용자가 선택한 파일을 편집·음량 정리한 후 AudioClip으로 연결 | -| 한국어 음성 | ‘하나’와 수집/세기 안내 | 하나~열 10클립, 공통 안내, 합치기/나누기 시범, 상태별 도움 문구 | 대본 → conda Qwen3-TTS GPU·1.7B·Sohee → WAV → 발음·음량 검수 → AudioClip | -| 배경음악 | 임시 음악 또는 무음으로 기능 검사 | 놀이용 반복 음악 1곡과 메뉴용 편집본을 초기 요청 규모로 사용 | 사용자 선택 후 반복 구간·음량 편집; 3개 테마에 같은 곡을 사용할 수 있음 | - -#### 캐릭터·배경·2D 제작 절차 - -1. R11의 외형을 기준으로 구, 얼굴·마스크, 연결 빛, 망토, 선택·계수 표시의 작은 스타일 시트를 만든다. 색·선 두께·광택·그림자 기준을 같은 시트에 기록한다. 기존 합체 영웅 그림은 참고 시안이며 그대로 3D 모델이 된 것으로 처리하지 않는다. -2. 구 하나와 2+3 합체, 5에서 떨어진 두 구를 선택한 분리를 먼저 Unity에서 표현한다. 기본·선택·계수 상태와 임의 입체 배열을 회전해 확인한 뒤 얼굴 그림·재질·소품을 확장한다. -3. AI 그림은 얼굴/마스크/아이콘/텍스처를 개별 요소로 생성한다. 공통 시안·스타일 지시를 재사용하고, 채택 결과의 여백·윤곽·투명 가장자리·색을 그림 편집 도구로 정리한다. 눈·입·마스크를 구 표면에 얹으며 숫자와 안내 문구를 이미지에 구워 넣지 않는다. 구 전체나 합체 몸체의 그림을 동적 3D 몸체로 대체하지 않는다. -4. Blender에서 둥근 모서리의 바닥·다리·받침·가림 소품을 공통 크기 기준으로 만든다. 불필요한 내부 면을 줄이고 피벗·스케일·회전·법선·UV를 정리한 뒤 선택한 모델을 FBX로 내보낸다. 모델 하나를 먼저 Unity에 넣어 방향·크기·재질·충돌 범위를 확인한 후 같은 방식으로 나머지를 제작한다. -5. Blender의 재질 노드가 Unity에 그대로 전달된다고 가정하지 않는다. Unity에서 공유 URP 재질과 제한된 색/텍스처 변형으로 장난감 질감을 구성한다. 단순 조명·그림자로 시작하고 후처리는 숫자와 개별 구가 읽히는 범위에서 조정한다. -6. 세 테마는 **정원 놀이터·구름 놀이터·별빛 공방**을 초기 미술 시안으로 제안한다. 이름·구체 소품·색은 조정할 수 있으며 테마 3개라는 범위는 유지한다. 정원은 잎/꽃/둥근 다리, 구름은 구름 받침/무지개 아치, 공방은 받침대/패널/별 모양 표지로 구별한다. 별·꽃 등 장식은 수량을 세는 자석과 외형·강조·입력에서 구별한다. -7. 배경은 카메라를 회전할 수 있는 입체 세트로 구성한다. 고정 카메라용 그림 한 장을 놀이터 전체로 사용하지 않는다. 테마는 스테이지 정의의 배치/장식 참조로 연결한다. 1~10/11~20/21~30에 각각 배정하는 것을 초기 배치값으로 두며, 배정 조정으로 R07의 순서·총량·목표를 바꾸지 않는다. -8. 모든 테마에서 자석은 장식 뒤·위에 놓을 수 있어도 회전하면 발견하고 집을 수 있어야 한다. 별도의 임무 장치·보행·새 성공 조건을 추가하지 않는다. 성공 후 자유 놀이와 이어하기에서도 해당 스테이지의 테마를 유지하며, 고정 정의와 `sourceStageId`에서 복원해 저장 필드를 늘리지 않는다. - -Krita·Inkscape 같은 그림 편집기와 Audacity 같은 음원 편집기는 파일 정리용 도구 후보이며 필수 런타임 의존성이 아니다. 실행 시 사용 가능한 도구를 확인하고 필요한 무료 편집 도구만 준비한다. Blender·그림/음원 편집기 설치는 확인하지 않았다. Qwen3-TTS 패키지 설치와 클래스 import는 확인했지만 GPU 추론·음성 파일 생성은 아직 검증하지 않았다. - -#### 특수 효과와 애니메이션 - -- 접촉 빛과 선택 강조는 공유 고리/윤곽 표현을 사용하되 형태·밝기·움직임을 다르게 한다. 이미 센 구의 표시는 지나가는 접촉 빛 뒤에도 유지한다. -- 자력 망토는 정지한 친구에게도 보이는 얇은 리본 메시로 만든다. 움직임에만 남는 TrailRenderer 하나로 망토 전체를 구현하지 않는다. 이동 꼬리는 보조로 사용할 수 있으며 망토는 안정 상태의 묶음마다 하나다. -- 성공은 짧은 작은 입자·표정·소리로 표현한다. 많은 입자나 큰 빛으로 구·목표를 덮지 않는다. 첫 버전에 전체 몸체 리깅·천 시뮬레이션·별도 대형 VFX 프레임워크를 요구하지 않는다. -- 떠오르기·기울기·망토 움직임은 표현용 하위 객체에서 실행한다. 기본 위치·구 ID·계수 기록은 바꾸지 않는다. 확정된 상태 변경과 후보/선택 미리보기를 구분해 재생하고 취소·후속 조작·복원 시 오래된 효과를 정리한다. - -#### 효과음·음악 요청표와 사용자 선택 - -음원을 요청할 때 다음 표와 함께 용도 ID, 실행 화면/행동, 느낌, 권장 길이, 반복 여부, 필요한 단계, 파일 형식, 출처/사용권 정보를 전달한다. 아래 길이는 편집용 초기값이며 사용자 음원과 조작감에 맞춰 조정한다. 용도별 파일은 공유할 수 있고, 초기 UI는 임시 소리로 연결할 수 있으나 사용자 선택과 실제 재생 검수가 끝나야 최종 완성으로 처리한다. - -| 용도 ID | 사용 시점·느낌 | 길이·반복 초기값 | 최초 필요 단계 | -|---|---|---|---| -| `sfx_pickup` | 친구를 집을 때, 가벼운 말랑함 | 0.1~0.3초, 1회 | T2 | -| `sfx_count` | 새 구를 셌을 때, 음성을 가리지 않는 짧은 톡 | 0.1~0.2초, 1회 | T2 | -| `sfx_ui` | 유효한 버튼 선택, 조용한 클릭 | 0.1~0.2초, 1회 | T2 | -| `sfx_success` | 퍼즐 성공, 부드러운 축하 | 1~2초, 1회 | T2 | -| `sfx_merge` | 합체 확정, 자석이 붙는 ‘착’ | 0.2~0.5초, 1회 | T4 | -| `sfx_split_select` | 분리 구 선택/해제, 작은 반응 | 0.1~0.2초, 1회 | T4 | -| `sfx_split` | 분리 확정, 연결이 풀리는 ‘톡’ | 0.2~0.5초, 1회 | T4 | -| `sfx_encourage` | 목표와 다른 구성/무효 조작, 부담 없는 반응 | 0.2~0.5초, 1회 | T4 | -| `sfx_spawn_return` | 자유 놀이 추가/반납 | 0.2~0.4초, 1회 | T5 | -| `sfx_hint` | 도움 강조, 주의를 부드럽게 끎 | 0.2~0.5초, 1회 | T6 | -| `music_play` | 퍼즐·자유 놀이, 가사와 긴장감 없는 반복 음악 | 60~120초, 반복 | T7 | -| `music_menu` | 시작/선택 화면, 같은 음악의 조용한 편집본 가능 | 30~60초, 반복 | T7 | - -사용자가 파일을 제공하면 그 파일을 검수하고, 출처나 후보를 선택하면 허용된 파일을 확보하여 편집한다. 구매가 필요한 후보는 비용 조건을 먼저 제시하며 승인되지 않은 구매를 실행하지 않는다. 사용권을 확인하지 못한 후보나 사용자가 아직 선택하지 않은 임시 파일을 최종 자산으로 승격하지 않는다. 사용자 응답이 필요한 동안에는 해당 소리의 임시 연결과 다른 구현을 진행하되, 최종 음원 완료 항목은 미완료로 남긴다. - -전달 규격의 초기값은 음성·효과음은 WAV/PCM 16bit·mono·44.1kHz, 음악은 WAV/PCM 16bit·stereo·44.1kHz다. 다른 형식의 허용된 파일을 받으면 원본을 보관하고 Unity 입력용 파일을 변환한다. TTS의 원래 출력 샘플레이트와 음악의 원래 채널 수는 보존 기록에 남기며, 샘플레이트·채널은 품질과 메모리를 확인해 조정할 수 있다. 숫자 클립의 이름은 대본표 ID와 일치시키고 잘리지 않은 한 발화를 담는다. - -#### 로컬 TTS 대본과 출력 계약 - -로컬 Qwen3-TTS는 **제작 PC에서 음성 파일을 생성하는 도구**다. Unity 앱과 빌드에 TTS SDK·모델·서버를 추가하지 않는다. 생성된 WAV를 읽으면 되므로 Qwen의 Python API를 게임 코드에 결합하지 않는다. 환경·모델·파일·품질을 아래 절차에 따라 각각 확인하고 클라우드 TTS로 자동 전환하지 않는다. - -| 음성 ID | 대본/생성 범위 | 연결 시점 | -|---|---|---| -| `count_01`~`count_10` | 하나, 둘, 셋, 넷, 다섯, 여섯, 일곱, 여덟, 아홉, 열 | 새 구를 센 순서의 개수; 숫자 기호를 TTS에 그대로 맡기지 않음 | -| `guide_collect` | ‘친구를 찾아 끌어와 볼까?’ | 수집 안내/도움 | -| `guide_count` | ‘모두 모았네! 하나씩 눌러 세어 보자.’ | 세기 안내 | -| `guide_merge` | ‘친구들을 가까이 붙여 볼까?’ | 합치기 시범/도움 | -| `guide_split` | ‘나누기를 누르고, 떼어 낼 친구를 골라 끌어내자.’ | 나누기 시범/도움 | -| `guide_find`, `guide_uncounted` | 찾을 자석/아직 세지 않은 구를 알려주는 짧은 문구 | R08의 현재 상태 도움 | -| `guide_split_N_K` | ‘네 개 친구에서 하나를 떼어 볼까?’처럼 실제 힌트에 쓰는 N·분리 개수 K별 문장 | 목표에 접근하는 분리 도움 | - -대본은 초기 문안이며 같은 의미의 자연스러운 한국어로 다듬을 수 있다. N에 따라 달라지는 조사는 문장 단위로 생성·검수하고 숫자 클립을 기계적으로 이어 붙여 어색한 문장을 만들지 않는다. 분리 안내 ID의 N은 2~10, K는 1~N-1이며 실제 도움에 사용하는 쌍을 대본표에 기록한다. 위 예시 목록만으로 실제 힌트의 모든 분리 수를 하나로 고정하지 않는다. T6에서 실제 화면 안내·힌트 종류와 대본을 대조해 필요한 문장을 빠짐없이 요청한다. - -실행 담당자가 음성 ID·화면 문구·발화 문구·사용 조건·필요 단계를 담은 대본표와 생성 프로필을 만들고 선택된 Qwen 환경에서 생성한다. 생성 도구명·버전·모델·목소리 식별자·설정과 사용 조건을 기록한다. WAV는 원본으로 보관하고 앞뒤 과도한 무음·잘림·잡음·발음 오류를 편집/재생성으로 수정한다. 모든 숫자와 실제 안내 문장을 듣고 확인하며 무음 파일·다른 숫자·누락을 통과시키지 않는다. 모델을 앱에 배포하지 않아도 결과물의 사용 가능 범위는 확인한다. - -#### Qwen3-TTS 제작 환경과 생성 절차 - -제작 도구는 로컬 Qwen3-TTS이며 기존 conda 환경 `qwen3-tts`를 재사용한다. **GPU·1.7B CustomVoice·한국어 Sohee**로 확정한다. 고정 목소리를 재사용하면서 말투 지시를 적용하는 방향이다. VoiceDesign·음성 복제·온라인 API·대화형 서버를 제작의 기본 경로에 추가하지 않는다. - -**2026-10-04 읽기 전용 확인 결과**는 아래와 같다. 패키지 설치 확인과 실제 음성 생성은 다르며 이 기획 보강에서는 환경 변경·모델 다운로드·추론·음성 파일 생성을 수행하지 않았다. - -| 항목 | 확인 결과 | 개발 실행에서 확인할 사항 | -|---|---|---| -| conda 환경 | `qwen3-tts` 존재; 환경의 Python 직접 실행 가능 | conda 실행 경로 또는 해당 환경 Python을 찾아 사용; 시스템 Python과 혼용하지 않음 | -| Python·Qwen | Python 3.12.15, `qwen-tts` 0.1.1, `Qwen3TTSModel` import 성공 | 실제 제작에 사용한 버전 기록과 샘플 생성 | -| 주요 패키지 | torch 2.14.1, transformers 4.57.3, soundfile 0.14.0, accelerate 1.12.0 | GPU 정비 후 필요한 호환 버전과 실행 결과 고정 | -| 현재 PyTorch | `torch.version.cuda = null`, `torch.cuda.is_available() = false` | CUDA 지원 PyTorch 구성과 실제 GPU 연산 검증 | -| 하드웨어 | RTX 4080 SUPER, 약 16GB VRAM, 드라이버 591.86 | 모델 로딩·실제 추론의 메모리/시간 확인; 하드웨어 존재만으로 통과 처리하지 않음 | -| import 경고 | FlashAttention 미설치, SoX 실행 파일 미발견; 클래스 import는 성공 | 경고와 실패를 구분하고 선택한 생성 경로의 필요성을 샘플로 확인 | -| 모델·음성 | 가중치 확보·모델 로딩·GPU 추론·WAV 생성 미검증 | 각각 실행 증거를 남기며 설치 완료로 대신하지 않음 | - -개발 실행에서는 기존 환경의 패키지 이름·버전과 GPU 상태를 먼저 기록한다. 공식 PyTorch의 Windows/Python/CUDA 설치 조합 중 Qwen과 호환되는 구성을 사용하여 필요한 PyTorch 변경만 수행한다. 환경 전체 삭제·재설치나 관련 없는 패키지 일괄 업그레이드는 하지 않는다. conda가 셸 PATH에 없으면 확인한 conda 실행 파일 또는 해당 환경 Python을 사용하며 전역 Python으로 대신 실행하지 않는다. - -CUDA가 활성화되고 작은 텐서 연산이 성공한 뒤 모델을 로딩한다. FlashAttention 설치를 성공 조건으로 삼지 않는다. 현재 Windows 환경에서는 `attn_implementation="sdpa"`를 초기값으로 사용하고 같은 GPU·모델에서 호환성 문제를 확인한 경우 `eager`로 조정할 수 있다. `device_map="cuda:0"`, `dtype=torch.bfloat16`, 배치 크기 1을 초기값으로 두고 장치 지원·메모리·음질을 확인해 dtype/배치를 조정한다. SoX 경고만으로 선택한 12Hz CustomVoice 경로를 실패 처리하거나 무조건 추가 설치하지 않는다. 실제 생성이나 채택한 편집 경로가 필요로 할 때 확인해 준비한다. - -| 생성 항목 | 값·규칙 | -|---|---| -| 모델 ID | `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` | -| 모델 revision | 첫 제작에서 공식 저장소의 커밋 해시를 기록하고 그 스냅샷을 고정; 이후 조용히 최신 버전으로 변경하지 않음 | -| 언어·목소리 | `language="Korean"`, `speaker="Sohee"`; 로드한 모델의 지원 목록과 대조 | -| 생성 함수 | `Qwen3TTSModel.from_pretrained()` → `generate_custom_voice()` | -| 초기 말투 지시 | ‘5~7세 아이에게 이야기하듯 따뜻하고 밝게, 천천히 또렷하게 읽어 주세요. 숫자는 정확하게 읽고 과장된 비명이나 웃음소리는 넣지 마세요.’ — 조정 가능 | -| 생성 방식 | 한 모델을 로드하여 선택한 대본들을 순서대로 생성; 처음에는 한 클립씩 검수 | -| 출력 | 함수가 반환한 파형과 실제 sample rate로 원본 WAV 저장; mono/PCM 16bit·44.1kHz의 Unity용 편집본은 별도로 준비 | - -가중치와 필요한 tokenizer·processor 파일은 게임 저장소 밖의 로컬 모델 캐시에 둔다. 최초 확보는 개발 단계의 모델 다운로드이며 앱의 네트워크 기능이 아니다. 고정 revision의 필요한 파일을 완전히 확보한 뒤 로컬 스냅샷 경로로 모델을 로드하여 구성 요소의 버전이 섞이지 않도록 한다. 설치된 Qwen wrapper에서 모델과 processor에 revision 인자가 모두 전달된다고 가정하지 않는다. 캐시 경로는 실행 환경에서 찾고 기기별 절대 경로를 게임이나 공용 제작 프로필에 박아 넣지 않는다. - -모델·목소리·언어·GPU 실행이 준비되지 않으면 오류 원인과 미검증 상태를 남긴다. 다른 제작 구성을 자동 선택하지 않는다. Unity 기능 개발은 기존 정책에 따라 임시 파일로 진행할 수 있지만 Qwen 제작·최종 음성 항목은 완료로 표시하지 않는다. CUDA 연산 확인만으로 실제 GPU 추론을 통과시키지 않는다. - -**개발하면서 만들 파일과 명령**은 다음과 같다. 현재 파일이나 실행 결과가 존재한다는 뜻이 아니다. - -| 제작 대상 | 역할 | -|---|---| -| `tools/audio/generate_voice.py` | 기존 환경에서 대본 선택 생성·일치 파일 재사용·파일 검사·생성 이력 기록; Unity 실행 코드와 분리 | -| `SourceAssets/Audio/Voice/voice-catalog.json` | `schemaVersion=1`, `clips` 배열. 각 항목은 고유 `id`, 비어 있지 않은 `text`(발화할 한국어 전체 문장), `usage`(화면/행동 설명), `stageIds`(1~30의 관련 번호), `required`(필수 여부)를 가짐 | -| `SourceAssets/Audio/Voice/qwen-profile.json` | 모델 ID/revision, 언어/목소리, 말투 지시, device/dtype/attention, seed와 실제 생성 설정; 환경별 캐시 절대 경로와 비밀 값은 포함하지 않음 | -| `SourceAssets/Audio/Voice/generated//` | 생성 회차별 원본 WAV와 `manifest.json`; ID·대본·프로필·결과의 대응을 보존 | -| `SourceAssets/Audio/Voice/edited/` | 채택한 편집 후보·편집 내역; 생성 원본을 덮어쓰지 않음 | -| `Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/` | 청각/형식 검수가 끝나 게임에 연결할 ID별 WAV | - -카탈로그에 `count_01`~`count_10`과 앞 대본표의 안내/실제 도움 문구를 기록한다. 같은 클립을 여러 스테이지에서 사용할 수 있다. 개별 회차에서는 `--ids`로 해당 단계에 필요한 ID만 생성하며, 전체 제작 완료 검사는 카탈로그 전체의 필수 항목을 대상으로 한다. 선택하지 않은 후속 단계의 음성이 첫 샘플 검사에서 없다는 사실과 전체 제작의 누락은 구분한다. - -생성 프로필의 필드는 `schemaVersion=1`, `modelId`, `modelRevision`, `language`, `speaker`, `instruct`, `device`, `dtype`, `attention`, `seed`, `generationParameters`다. `seed`는 초기값 42로 두고 생성 설정과 함께 조정할 수 있다. `generationParameters`에는 실제 적용한 생성 인자와 모델의 기본 설정을 기록한다. 특정 seed가 음질을 보증하거나 장치/라이브러리가 달라도 동일한 WAV를 보장한다고 주장하지 않는다. - -manifest는 `schemaVersion=1`, 고유 `runId`, 생성 시각, 실제 패키지·장치 정보, 고정 모델 revision과 카탈로그/프로필 해시, `clips` 결과를 가진다. 각 클립은 `id`, 대본 해시, 해당 생성 설정의 해시, `result`(`generated`·`reused`·`failed`), 원본 생성 회차/파일 참조를 기록한다. 성공 결과에는 WAV 해시·실제 sample rate·채널·샘플 수·길이, 실패에는 오류를 남긴다. 생성 성공 표시는 청각 검수 통과 표시가 아니다. 채택한 원본과 Unity 편집본·검수 결과는 자산 기록에서 연결한다. - -파일 재사용은 ID만으로 판단하지 않는다. 같은 대본·모델 revision·목소리·말투·실제 생성 설정과 파일 해시가 일치하는 원본만 재사용하며 원래 생성 이력을 참조한다. 문구·말투·모델 설정이 바뀌면 바뀐 클립을 새 회차의 후보로 생성하고 기존 채택 파일을 조용히 덮어쓰지 않는다. 생성 실패·중단에서 빈 WAV를 정상 파일로 등록하지 않는다. 새 파일이 검수될 때까지 기존 채택본은 유지하되, 대본/프로필이 바뀌어 현재 조건과 맞지 않는 기존본을 최신 검수 통과로 처리하지 않는다. - -다음은 구현할 진입점의 계약이다. 기본 샘플 → 부분 확장 → 전체 생성 → 파일 검사 순으로 사용하며 실제 실행 후에만 증거를 기록한다. - -```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 count_01,count_04,count_07,count_10,guide_collect -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 -$qwenVoiceManifest = '<생성 도구가 출력한 manifest.json 경로>' -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 --validate-only --manifest $qwenVoiceManifest -``` - -`--ids`를 생략하면 카탈로그 전체를 생성한다. `--validate-only`는 명시한 manifest와 현재 카탈로그/프로필에 대해 파일·이력 검사만 하고 모델을 로드하거나 다운로드하지 않는다. 형식 오류·선택한 필수 ID 누락·생성 실패·해시 불일치는 0이 아닌 종료 코드와 ID별 오류로 보고한다. 검사 범위가 부분 ID인지 전체 필수 목록인지 기록한다. - -**검증 순서와 합격 기준**은 다음과 같다. - -1. 환경·패키지/import → CUDA 및 실제 장치 연산 → 고정 모델 로딩 → 실제 GPU 생성의 결과를 각각 기록한다. 생성 시간·사용 장치·메모리·반환 sample rate와 로그를 남긴다. 설치 확인으로 이후 검사를 통과시키지 않는다. -2. ‘하나·넷·일곱·열’과 수집 안내를 첫 샘플로 생성하여 숫자 발음·짧은 말의 잘림·Sohee 목소리·친근한 말투를 듣고 확인한다. 문제가 있으면 같은 승인된 모델/목소리에서 대본·말투·생성 설정을 조정해 다시 검수한 뒤 전체 생성으로 확대한다. -3. 자동 파일 검사는 카탈로그 ID/본문/번호 유효성, 선택 범위의 필수 ID, WAV 읽기·양수 길이/sample rate·유효 채널·유한 파형, 파일/대본/프로필 해시와 생성 결과를 확인한다. 원본의 반환 sample rate를 그대로 기록하며 편집본만 전달 규격에 맞춘다. 무음·클리핑 후보는 음질 검수 대상으로 보고하고 잘못된 발음을 자동 통과로 판정하지 않는다. -4. 모든 숫자 10개와 실제 게임에서 사용하는 안내·도움 문장을 청각 검수한다. 다른 단어/숫자·발음 오류·문장 잘림·음색 불일치·과장된 소리가 남으면 재생성/편집한다. 이 검수 결과와 채택 파일 해시를 자산 기록에 남긴다. -5. 채택 원본을 보존한 채 앞뒤 무음·음량을 정리하고 Unity용 WAV를 만든다. 현재 카탈로그/프로필과 채택본의 대응, 파일 ID, mono/PCM 16bit·44.1kHz의 초기 전달 규격을 확인한다. 반환 sample rate를 표기만 바꾸거나 새로 샘플링했다고 원래 품질이 올라간 것으로 간주하지 않는다. -6. Unity에서 숫자 읽기·안내·순차 재생·마지막 계수·음악 혼합·전환 정리를 검사하고, 같은 최종 APK의 AVD 오프라인 재생으로 확인한다. 제작 PC에서 Qwen이 실행됐다는 사실과 앱에서 올바른 파일이 재생된다는 사실을 별도 증거로 남긴다. - -공식 코드/선택 모델 revision의 라이선스와 모델 카드·목소리 출처를 확인해 기록한다. 모델이 Apache-2.0으로 표시되어 있다는 사실을 음성 결과물의 독점권이나 음질 보증으로 바꾸지 않는다. 제작 도구에 원본 음성 입력·복제를 요구하지 않으며 Qwen 패키지·모델 캐시는 APK에 넣지 않는다. 기능 개발의 전체 검증 진입점은 기존 `tools/verify.ps1` 계약을 유지하고, 음성 제작 도구 검사는 각 단계에서 파일 증거를 제공한다. - -#### Unity 오디오 연결 - -- Mixer는 Master 아래 Voice·SFX·Music으로 나눈다. Presenter가 확정된 조작/상태에 대응하는 소리 ID를 전달하며 오디오 재생 완료가 수학 상태·성공 판정을 결정하지 않는다. 숫자 읽기·안내·UI 소리는 카메라 회전으로 작아지지 않는 2D 재생을 기본으로 한다. -- 빠른 계수 탭은 입력과 계수 표시를 즉시 확정하고 숫자 음성을 계수 순서대로 재생한다. 같은 구를 다시 누르면 새 계수 음성을 추가하지 않는다. 최대 10개 안에서 음성이 서로 겹치거나 중간 숫자가 누락되지 않게 한다. -- 성공 후 같은 보드에서 자유 놀이로 넘어갈 때 대기 중인 마지막 숫자 읽기는 끝까지 재생한다. 축하 소리와 안내가 이를 덮지 않도록 낮추거나 뒤로 배치한다. 성공 후 자유 놀이의 새 조작도 막지 않는다. -- 화면/스테이지 전환과 앱 일시 정지에는 이전 음성 큐·효과를 정리한다. 복원 시 지난 숫자·성공 안내를 다시 재생하지 않는다. 상태가 바뀐 뒤에는 이전 상태용 대기 안내를 취소하며 계수의 숫자 순서를 안내로 끊지 않는다. -- 음성 중에는 음악을 낮추고 효과음도 발음을 덮지 않게 섞는다. 오답에 큰 경고음이나 벌점 느낌을 넣지 않는다. 재생 우선순위·음량·짧은 반복 제한은 듣고 조정한다. 별도 설정 화면 추가는 이번 자산 계획에 포함하지 않는다. -- 원본은 WAV로 보관한다. 짧은 효과음·숫자 음성은 mono·Decompress On Load·Preload Audio Data를 초기값으로 사용하고 긴 음악은 Streaming을 초기값으로 둔다. 실제 길이·메모리·지연을 Profiler와 Android 재생으로 확인해 조정한다. 압축된 원본을 다시 무손실로 바꿨다고 품질이 복구된 것으로 간주하지 않는다. - -#### 원본·출처·진행 기록 - -개발 단계에서 `SourceAssets/Models/`의 .blend, `SourceAssets/Images/`의 편집 원본, `SourceAssets/Audio/`의 원음·대본·편집 자료를 보관한다. AI 입력/채택 결과/정리 과정을 `SourceAssets/Generation/`에 기록한다. Unity가 가져오는 FBX·PNG·WAV 및 재질·프리팹은 `Assets/MagnetismHeroes/UnityRuntime/Presentation/` 아래 Models·Textures·Materials·UI·VFX·Audio·Prefabs에 둔다. 제작 원본은 빌드 포함 대상으로 참조하지 않는다. TTS 모델·가상 환경·도구 캐시는 게임 소스/앱에 넣지 않는다. - -개발하면서 `docs/asset-register.md`에 실제 자산 목록과 출처를 만든다. 자산 ID는 고유하며 최소한 다음 열을 기록한다. - -| 기록 | 내용 | -|---|---| -| 식별·연결 | 자산 ID, 유형, 원본/Unity 파일 경로, 사용 화면·스테이지·용도 ID | -| 출처·권리 | 제작자/제공자, 원본 URL 또는 자체 제작, 확보 날짜, 라이선스·조건 증거와 필요한 고지 | -| 생성·수정 | 도구/모델 버전, 대본/프롬프트·설정, 채택 파일, 편집 내역; 도구가 제공하는 재현 정보도 보관 | -| 선택·검수 | 사용자 선택이 필요한 음원의 선택 결과, 임시/최종 구분, import·화면·소리 검수 결과와 증거 경로 | - -CC0 등 무료 자산도 실제 파일과 해당 출처의 조건을 기록한다. AI 결과에 일괄적으로 독점권이나 무조건적인 상업 이용 가능성을 부여하지 않는다. 필요한 사용 고지는 개발 단계의 `ThirdPartyNotices.txt` 등 배포 가능한 파일에 모은다. 임시 자산이 정상 재생되었다는 기록과 최종 파일의 권리·품질 검수 완료를 구분한다. - -#### 단계별 자산 완성 기준 - -| 단계 | 자산 작업과 완료 판단 | -|---|---| -| T2·T3 | 첫 구·얼굴·숫자·기본 UI·입체 임시 배경과 ‘하나’ 음성 연결; Android에서 포함 파일·한글·오프라인 재생을 조기 확인. 임시 소품·음원은 최종 완료가 아님 | -| T4 | 대표 합체·분리의 접촉빛·선택·표정/망토 전환 시제품; 실조작에 연결해 구/표시 보존 확인, 필요한 효과음 요청 | -| T5·T6 | 자유 놀이·도움·시범용 음원/대본 요청; 3개 테마의 입체 세트와 30개 배치 연결, 모든 구의 발견·조작 가능성 확인 | -| T7 | 사용자 선택 음원과 Qwen3-TTS 생성 파일, 최종 얼굴·UI·모델·재질·망토·VFX를 통합; 임시 파일 교체와 전체 출처 기록·품질 검수 완료 | -| T8 | 최종 자산을 포함한 동일 APK로 세 테마·오프라인 소리·연속 조작·복원을 검사; 실제 기기 성능 판정은 후속으로 구분 | - -자산 합격 기준은 다음과 같다. 픽셀 모양을 고정하는 자동 검사나 테스트 수 늘리기로 시각·청각 검수를 대신하지 않는다. - -- **모델·재질:** FBX 방향/스케일/피벗이 맞고 분홍색 오류 재질·뒤집힌 면·필수 참조 누락이 없다. 3개 테마가 실제로 구별되며 모든 배치에서 회전해 자석을 발견·선택할 수 있다. -- **얼굴·UI·효과:** 투명 가장자리·한글 글리프·숫자 잘림을 화면 비율별로 확인한다. 1~10과 직선/평면/입체 배열의 구 경계·대표 얼굴/숫자/망토 개수, 선택·계수 가독성, 연속 조작/취소/복원 정리를 R11대로 확인한다. -- **오디오:** 요청 용도와 실제 파일/대본이 일치하고 모든 숫자·안내를 들어 발음·잘림·잡음·음량을 확인한다. 빠른 계수 입력의 순서·마지막 숫자·오래된 안내 취소를 검수한다. 반복 음악은 실제 게임에서 최소 3회 반복하며 경계의 끊김·클릭음을 확인한다. -- **원본·권리·빌드:** 최종 사용 파일에 출처/조건 기록이 있고 필요한 고지를 포함한다. 임시·미선택·누락 파일을 완료 처리하지 않는다. 앱에 TTS 모델/SDK/키/다운로드 경로가 없어도 오프라인 재생되며 실제 빌드 포함 파일을 검사한다. -- **모바일 비용:** 처음에는 공유 재질·텍스처와 적은 입자로 구성한다. 얼굴/UI 텍스처는 512~1024px을 초기값으로 두고 결과에 맞춰 조정한다. 구 10개·망토/연결 빛·음성·음악·가장 많은 소품의 상태에서 드로우콜·투명 오버드로·메모리·음성 로딩·프레임 비용을 기록한다. 이 조정값이나 AVD의 FPS를 실제 스마트폰 합격 기준으로 선언하지 않는다. - -### 고정 정의와 플레이 상태 - -| 구분 | 제작·저장 방식 | 수명 | -|---|---|---| -| 고정 스테이지 | Inspector에서 편집하는 ScriptableObject; R07·R12 필드와 배치 | 제작 시 변경, 실행 시 읽기 전용 | -| 런타임 정의 | 고정 자산을 변환한 순수 C# 데이터 | 스테이지 실행에 사용 | -| 플레이 상태 | 구 ID·위치·묶음·수집·계수·카메라 | 조작을 확정할 때 변경 | -| 이어하기 | R12의 JSON 버전 1, 퍼즐별 슬롯과 최근 자유 놀이 하나 | 앱 재실행 후 복원 | -| 화면과 연출 | 상태에서 계산한 얼굴·숫자, 임시 선택·애니메이션 | 다시 생성 가능, 별도 저장하지 않음 | - -Inspector 편집과 빌드 전 검사에서 총량·빈 묶음·잘못된 번호·누락된 배치 참조를 발견한다. 실제 정답은 Core 규칙으로 판정한다. 편집기 검사를 통과했다고 구를 실제로 발견·조작할 수 있다고 판정하지 않는다. - -## 4. 기능별 기획에서 구현까지의 반복 절차 - -각 기능은 아래 항목을 계획의 해당 작업 또는 작업 변경 설명에 먼저 정리한다. 별도의 기획 시스템이나 대규모 문서 묶음을 만들지 않는다. - -1. **학습·놀이 목적과 요구사항:** 어떤 아이의 행동을 가능하게 하는지와 R번호를 적는다. -2. **입력과 상태 변화:** 시작 상태, 아이의 조작, 확정 시점, 결과 상태, 화면·소리 반응을 적는다. -3. **불변 조건과 경계:** 수량·ID 보존, 무효 선택, 조작 충돌, 저장·재진입의 기대 결과를 적는다. -4. **합격 사례:** 준비 상태/행동/기대 결과를 작성하고 자동 검사와 수동 플레이 검사를 구분한다. -5. **최소 구현과 연결:** 규칙과 그 경계를 검사한 뒤 프리팹·UI를 연결한다. 한 기능의 정상 흐름이 플레이되도록 만든다. -6. **검증과 수정:** 해당 기능의 검사와 대표 플레이를 수행한다. 실패하면 원인을 수정하고 관련 검사를 반복한다. 단계가 끝나면 전체 회귀 검사를 수행한다. - -수학 상태·저장·입력 충돌처럼 오류 비용이 큰 부분은 합격 사례를 자동 테스트로 먼저 구현한다. 색·장식·단순 문구마다 구현을 그대로 복제하는 테스트를 만들지 않는다. 버그 수정에는 재현 가능한 검사나 수동 재현 절차를 남긴다. 기능 이름만 있고 판정할 결과가 없으면 구현 준비가 끝난 것이 아니다. - -### 대표 기능의 설계 카드 - -| 기능 / 근거 | 준비 상태와 행동 | 확정 결과 | 경계·검증 방법 | -|---|---|---|---| -| 모아 세기 / R05 | N개가 있는 놀이터에서 직접 끌어 모으고 각각 탭 | 전부 수집·한 묶음·전부 계수할 때만 성공 | 1번도 수집 필요; 미수집/중복 탭 거부. Core 검사 + 실제 화면 | -| 합체 / R02·R06 | 2와 3인 친구를 가까이 끌기 | 구 ID 5개를 보존한 친구 하나, 얼굴·숫자 5 | 이동 프레임마다 중복 합체하지 않음. 규칙 + 입력 연결 | -| 분리 / R04 | 5에서 나누기, 떨어진 구 2개 선택 후 끌기 | 선택 2와 나머지 3, 얼굴·숫자 각각 하나 | 0개·전체 선택은 불변; 선택만 해서는 분리 안 됨 | -| 입체 조작 / R03 | 친구/빈 공간/UI 위에서 터치를 시작 | 친구 이동/시점 회전/UI 조작을 구분 | 핀치·취소·영역 밖 놓기에서 구 소실 없음. PlayMode + Android | -| 성공과 자유 놀이 / R08·R09 | 목표 구성을 만들기 | 완료를 한 번 확정하고 같은 공간 자유 놀이 | 잘못된 구성은 계속 조작; 공급 11번째 거부, 0개에서 복구 | -| 이어하기 / R10·R12 | 두 퍼즐과 자유 놀이를 별도로 변경 후 재실행 | 세 상태와 ID·배치·계수·카메라 각각 복원 | 재플레이 완료 기록 유지, 손상·저장 중단 검사 | -| 도움 / R08 | 현재 수집·계수·구성 상태에서 도움 누르기 | 지금 가능한 대상/첫 행동 강조 | 자동 정답 실행 없음; 현재 상태에 맞는 안내 검사 | - -단계별 기능 목록은 위 카드 형식을 사용하되 합격 사례는 기획서의 전체 경계를 포함한다. 예를 들어 합체 테스트 하나가 저장·분리·힌트까지 대신 검증하지 않는다. - -## 5. 개발·테스트 환경의 역할 - -**주된 반복 작업은 Unity 에디터에서 수행한다. Android 검증은 첫 퍼즐, 중요한 연결 변경, 최종 완성 시점에 수행한다.** - -| 환경 | 확인하는 내용 | 실행 시점 | -|---|---|---| -| EditMode | 순수 C# 규칙·상태 전이·스테이지 정의·저장 검증 | 규칙·데이터·저장 변경마다 관련 검사, 단계 종료 시 전체 | -| PlayMode | 입력과 규칙 연결, 프리팹·숫자·얼굴, 전환·복원, UI 입력 차단 | 기능 연결마다 관련 검사와 수동 플레이 | -| Game View / Device Simulator | 가로 화면, 스마트폰·태블릿 화면 비율, 안전 영역과 글자 | UI·카메라·배치 변경 시 | -| Android Studio AVD의 실제 APK | 빌드·설치·앱 실행·Android 파일 저장·재시작·오프라인 | 첫 퍼즐에서 조기 검사; 입력·저장·빌드 설정 변경 시 관련 재검사; 최종 전체 | -| 실제 Android 기기 | 실제 다중 터치·손맛·가독성·성능·발열·배터리 | 기기 준비 후의 후속 범위 | - -Input System의 마우스 터치 시뮬레이션은 단일 포인터 개발을 돕는다. 핀치는 테스트 입력으로 두 접점을 주입하고 에뮬레이터의 지원 입력으로 연결도 검사한다. 마우스 한 개로 플레이했다는 이유로 실제 두 손가락 조작을 검증했다고 기록하지 않는다. - -Unity Device Simulator는 화면·안전 영역 검사 도구다. Android Studio AVD는 APK를 설치하는 별도의 운영체제 환경이다. 둘을 같은 검증으로 처리하지 않는다. Device Simulator는 대상 그래픽 백엔드와 실제 기기 성능을 재현하지 않는다. [Unity Android 시뮬레이터 설명](https://docs.unity3d.com/6000.6/Documentation/Manual/android-device-simulator.html) - -프로젝트를 열어 둔 에디터와 별도 배치 프로세스가 같은 프로젝트를 동시에 수정하지 않도록 검사 진입점에서 실행 방식을 정리한다. 정상 개발 반복은 열린 에디터의 Test Runner를 사용할 수 있고, 단계별 결과를 기록할 때는 같은 테스트 목록으로 검증 진입점을 사용한다. - -## 6. 테스트 합격 기준과 증거 - -합격 기준은 **요구사항 → 실패할 수 있는 경계 → 검사 사례 → 실행 증거** 순서로 만든다. 테스트 개수나 코드 커버리지 수치만으로 제품 완료를 선언하지 않는다. - -| 대상 | 필수 합격 기준 | 증거 | -|---|---|---| -| 규칙 | 1~10의 유효 합체·분리에서 구 ID 집합 보존; 구마다 소속 하나; 빈 묶음 없음 | EditMode 결과 | -| 분리 경계 | 크기 1~10의 모든 비어 있지 않은 진부분집합 선택 검사; 0개·전체·다른 묶음 선택 거부 | 매개변수화 테스트; 실제 분리 연결은 별도 | -| 정답·계수 | [2,2]와 [4] 구분; 중복 탭 불가; 수집·한 묶음·계수 조건 모두 필요 | Core·진행 검사 | -| 스테이지 | R07과 30개 정의 일치; 번호·총량 유효; 실제 규칙 호출로 모든 목표에 도달 가능 | 정의 비교 + 해답 경로 검사 | -| 입력·표현 | 같은 터치를 UI/이동/회전/계수에 중복 사용하지 않음; 취소·핀치 전환 복구; 표시 수량 일치 | PlayMode 결과 + 플레이 기록 | -| 합체 영웅 | 2+3→5와 5→2+3의 얼굴·숫자·망토 수 일치; 1~10 구 보존; 다른 모양의 5가 같은 판정; 선택·계수 표시 식별; 연속 조작·복원 후 잔상 정리 | PlayMode 상태 연결 검사 + 모바일 화면 비율에서 수동 외형 검사 | -| 저장 | 두 퍼즐+자유 놀이 독립 복원; 성공 전이 일관성; 재플레이 완료 기록 불변; 저장 중단·손상 복구 | 파일 검사 + Android 재실행 증거 | -| 자산 | 원본 정의 불변; Missing Script/참조 누락 없음; 한국어 글자·필수 음성 실제 포함·오프라인 재생 | 자산 검사 + 화면·소리 점검 | -| 자산 제작·인수 | 세 테마 실제 구별·모든 구 접근 가능; 최종 파일의 출처·사용 조건·검수 기록; 사용자 선택 음원·로컬 TTS 출력 연결; 순차 발화·마지막 숫자·음악 반복·전환 정리 | 자산 목록 + 에디터 검수 + Final APK 오프라인 재생 기록 | -| Android 조기 | 첫 퍼즐 설치·실행·미완료 위치/카메라/계수 복원·완료 후 자유 놀이 진입 | FirstSlice APK·기기·로그·화면 | -| Android 최종 | 대표 흐름과 저장·해금·오프라인을 완성 APK에서 재검사 | Final APK·로그·화면·체크 결과 | - -전체 목표 도달 검사는 단순히 목표 배열을 정답 함수에 넣는 방식으로 대체하지 않는다. 초기 상태에서 실제 합체·분리 호출을 통해 도달하고, 수 세기는 수집·계수 호출을 모두 수행한다. 시각적인 발견 가능성과 조작 가능한 배치는 화면에서 별도 확인한다. - -해당 단계의 필수 검사 실패 0개, 필수 검사 누락·미실행 0개를 합격 조건으로 삼는다. 전체 테스트 실행 수가 0이면 성공이 아니다. 정상 플레이에서 처리되지 않은 예외와 예상하지 못한 오류 로그가 없어야 한다. 손상 저장 같은 부정 사례의 의도된 실패는 테스트에서 기대 결과로 확인한다. - -각 단계 기록은 요구사항/사례 ID, 실행 시각, 에디터·패키지 버전, 소스 커밋과 미커밋 변경 유무, 실행 명령, 실행·통과·실패·미실행 수, 로그 위치를 가진다. Android 기록에는 APK 해시, AVD/API/ABI/그래픽 설정, 설치·실행 여부, 동작별 결과와 화면 증거를 추가한다. 증거를 만든 후 소스나 자산이 달라지면 영향을 받는 검사와 최종 빌드를 다시 수행한다. - -실제 기기가 없으므로 FPS·발열 합격을 이번 에뮬레이터 수치로 정의하지 않는다. 최대 10개 상태에서 Unity Profiler로 프레임·할당·렌더링·음성 비용을 관찰하고 명확한 불필요 비용을 줄인다. 실제 기기 성능 기준과 최종 판정은 후속 검증에서 정한다. 탭/끌기 임계값, 흡착 거리, 카메라 범위, 연출 시간은 조정 가능한 값으로 관리한다. - -## 7. 검증 진입점의 계약 - -공통 준비에서 `tools/verify.ps1`을 만들고 아래처럼 프로젝트 루트에서 실행한다. 아래 명령은 현재 존재하는 명령이 아니다. - -```powershell -pwsh -NoProfile -File tools/verify.ps1 -Phase EditMode -Milestone FirstSlice -pwsh -NoProfile -File tools/verify.ps1 -Phase PlayMode -Milestone FirstSlice -pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidBuild -Milestone FirstSlice -pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidSmoke -Milestone FirstSlice -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 -``` - -`Phase`는 위 네 값, `Milestone`은 `FirstSlice`, `Composition`, `Persistence`, `Content`, `Final`이다. 생략한 Milestone은 Final이다. 초기 단계는 그때 구현한 기능의 사례만 대상으로 삼고, Final은 기획서 전체를 검사한다. 초기 결과를 최종 전체 결과로 재사용하지 않는다. - -| 단계 | 남길 결과 | -|---|---| -| EditMode | `artifacts/verification//editmode.xml`, 실행 로그, 검사 범위 | -| PlayMode | `artifacts/verification//playmode.xml`, 실행 로그, 검사 범위 | -| AndroidBuild | `artifacts/android//MagnetismHeroes.apk`, 빌드 로그·설정·해시 | -| AndroidSmoke | `artifacts/verification//android-smoke.md`, 기기 설정·로그·화면 증거 | - -마지막 APK는 `artifacts/android/MagnetismHeroes.apk`에도 제공하며 Final 검사 APK와 해시가 같아야 한다. 에디터·Android 도구는 설치 환경에서 찾고 특정 사용자 폴더를 소스에 고정하지 않는다. 프로세스 종료 코드뿐 아니라 검사 결과 파일·실행 수·실패·누락과 APK 존재도 확인한다. - -AndroidSmoke는 APK 설치·앱 실행과 재현 사례의 증거를 모으는 진입점이다. 자동화할 수 없는 화면·소리 사례는 번호가 있는 수동 절차와 결과로 확인하며, 설치 성공만으로 이를 자동 통과시키지 않는다. 에뮬레이터 실패·미실행은 비정상 종료와 이유를 남긴다. ABI 불일치·가상화·그래픽 오류는 제품 규칙 실패와 구분하되 완료된 검사로 표시하지 않는다. - -## 8. 단계별 구현 작업 - -### T1 — 순수 C# 규칙과 상태 계약 - -**목표:** Unity 없이 구·묶음·계수·정답·공급 규칙을 검증하고 이후 단계의 공통 데이터와 진행/저장 경계를 마련한다. - -**근거:** R02, R04, R05, R06, R09, R12, R13. - -**작업 대상:** `Assets/MagnetismHeroes/Core/`, `Tests/EditMode/Core/`. - -**동작 계약:** 값은 구 개수에서 계산하고 구마다 묶음 하나를 보장한다. 합체·분리는 ID 집합을 보존한다. 무효 선택은 상태를 바꾸지 않는다. 목표 다중집합을 비교하고 수집·계수 ID는 이동·합체·분리로 초기화하지 않는다. 자유 놀이 공급·반납은 0~10 경계를 지키며 퍼즐 중에는 거부한다. R12의 정의·저장 데이터 필드와 제약을 보존한다. - -**검증:** 모든 유효 분할, 다른 묶음 ID·중복 ID·빈 묶음·[2,2]/[4]·계수 경계·10개 한도를 검사한다. Core에 Unity 참조가 없고 EditMode가 통과해야 한다. - -**공유 작성:** 공통 패키지·어셈블리·씬은 실행 담당자가 관리한다. 이 단계는 전용 Core 코드와 검사만 작성한다. - -### T2 — 1번 수 세기의 첫 완성 흐름 - -**목표:** 1번을 시작해 직접 수집·계수하고 성공 후 같은 공간의 기본 자유 놀이로 들어가는 작동하는 흐름을 만든다. 성공 전 저장과 재시작 복원도 연결한다. - -**근거:** R01, R02, R03, R05, R08, R09, R10, R11, R12, R13, R14. - -**작업 대상:** Core의 진행 제어, `UnityRuntime/Interaction/`, `StageAssets/`의 1번 정의, `Presentation/`의 기본 화면·친구, `Persistence/`의 기본 저장, Qwen 생성 도구·초기 대본/프로필과 샘플 WAV, 대응 EditMode·PlayMode 검사. - -**동작 계약:** 시작/선택에서 1번을 실행한다. 한 구도 직접 끌어 수집하고 한 번 눌러야 성공한다. 친구 이동·빈 공간 회전·핀치를 구분하고 UI 입력을 차단한다. 성공 전 구·카메라·계수 상태를 저장하며 성공 후 완료·퍼즐 제거·자유 놀이 생성은 같은 변경으로 처리한다. 숫자·얼굴과 같은 공간 전환을 최소 표현으로 연결한다. - -**검증:** 1번 수집 생략 거부, 미완료 저장·복원, 중복 성공 거부, 카메라·조작 취소, 기본 자유 놀이 전환의 EditMode·PlayMode 및 실제 에디터 플레이를 통과한다. - -**단계 범위:** 첫 저장부터 R12의 JSON 버전 1과 전체 필드 경계를 사용한다. 지원하는 플레이 경로는 먼저 1번이고 나머지는 T4~T6에서 확장한다. 임시 폰트·외관·소리와 제한된 콘텐츠를 최종 R11·30개 완성으로 표시하지 않는다. - -구·기본 얼굴·숫자·UI·단순 입체 배경을 만들고, ‘하나’ 및 기본 안내 대본과 첫 효과음 요청을 준비한다. Qwen 환경의 GPU 지원을 정비하고 생성 도구·초기 카탈로그/프로필을 만들어 한국어 샘플과 필요한 첫 음성을 생성·검수한다. 중복 ID·잘못된 본문·누락/손상 WAV·대본/프로필 불일치의 파일 검사와 부분 재생성의 기존본 보존을 확인한다. 포함 파일의 한글·음성 재생을 연결하며, Qwen 출력이 준비되기 전의 임시 소리 검사와 최종 파일 검수를 구분한다. - -**공유 작성:** 기능 코드는 전용 경로에 작성한다. 실행 담당자가 시작/플레이 씬·참조를 연결하고 FirstSlice 검사 범위를 기록한다. - -### T3 — 첫 Android APK와 가상 기기 검사 - -**목표:** 첫 퍼즐을 실제 Android 앱으로 설치·실행하고 파일 저장·재시작을 확인해 플랫폼 문제를 일찍 발견한다. - -**근거:** R01, R03, R05, R09, R10, R11, R12, R13, R14. - -**작업 대상:** `Editor/`의 빌드 진입점, `tools/verify.ps1`의 Android 단계, FirstSlice 결과 경로, Android AVD. - -**동작 계약:** AVD를 실행하고 adb 연결·ABI·그래픽 지원을 확인한다. APK의 대상 설정과 일치시켜 빌드·설치한다. 가로 화면·한국어 기본 표시·1번 수집/계수·같은 공간 자유 놀이를 확인한다. 성공 전 이동·카메라 변경을 저장하고 종료·재실행해 복원한다. 오프라인 실행을 확인한다. - -**검증:** FirstSlice AndroidBuild·AndroidSmoke 실제 통과, APK·해시·로그·화면·절차별 결과를 남긴다. 빌드 성공만으로 실행 성공을 표시하지 않는다. - -기본 모델·재질·글꼴과 준비된 음성/효과음이 APK에 포함되고 오프라인에서 재생되는지 확인한다. 이 단계에 사용한 임시/최종 자산과 아직 제공되지 않은 파일을 기록한다. 세 테마·최종 음악 전체의 검수는 T6~T8에서 수행한다. - -1번은 한 번 세면 바로 성공하므로 성공 전 저장의 계수 목록은 비어 있다. FirstSlice에서는 이 빈 목록과 미완료 배치 복원을 검사한다. 일부 구만 센 미완료 상태의 복원은 T5의 저장 검사와 T6·T8의 N≥2 실제 스테이지 검사에서 확인한다. - -**이유와 순서:** 첫 퍼즐의 Android 검사 후 확장하는 순서는 사용자와 정한 개발 방식이다. 환경 문제가 막으면 원인·미검증 항목을 보고하고 원인을 해결한다. 이 단계 실패를 숨겨 완료 처리하지 않는다. - -**공유 작성:** 빌드 설정과 검사 진입점은 실행 담당자가 통합한다. 이 결과는 최종 검사를 대체하지 않는다. - -### T4 — 합체·분리·혼합 퍼즐의 완성 흐름 - -**목표:** 6번·10번·14번을 대표로 합체·분리·혼합 조작을 기존 플레이 흐름에 추가한다. - -**근거:** R02, R03, R04, R06, R07, R09, R11, R12, R13, R14. - -**작업 대상:** Core 진행 제어 확장, Interaction과 기본 Presenter·프리팹, 대표 정의 자산, 대응 검사. - -**동작 계약:** 가까운 친구에 붙이면 한 묶음이 된다. 나누기 → 구 선택 → 끌어내기로 선택/나머지를 각각 한 친구로 모은다. 떨어진 선택도 허용하며 분리 직후 의도하지 않은 재합체를 방지한다. 무효 선택은 상태 유지, 취소는 확정 상태 복구다. 14번은 행동 이력과 무관하게 최종 [2,2]로 판정한다. 회전해 공중 모든 방향을 편집할 수 있다. - -**검증:** 6·10·14번 실제 규칙 해답과 화면 연결, 0개/전체/떨어진 선택, 핀치 전환·영역 밖 취소, 얼굴·숫자 일치를 검사한다. Composition EditMode·PlayMode와 변경된 입력의 Android 사례를 확인한다. 제작 중 대표 번호 직접 실행은 개발용 검사에만 사용하며 최종 순차 해금을 우회하지 않는다. - -접촉 빛·선택·얼굴/망토 통합과 분리의 시제품을 실조작에 연결한다. 합체·분리 효과음을 요청하고 시범 대본을 Qwen 카탈로그에 추가해 생성·검수한다. 취소·연속 입력에서 상태보다 앞선 효과가 남지 않는지 확인한다. 최종 외형은 T7에서 마감한다. - -**공유 작성:** 실행 담당자가 단계 씬을 갱신한다. 이전 1번 흐름과 FirstSlice 검사도 유지한다. - -### T5 — 저장·진행·자유 놀이 전체 기능 - -**목표:** 첫 저장을 모든 미완료 슬롯과 최근 자유 놀이로 확장하고 10개 공급·반납과 진행의 수명을 완성한다. - -**근거:** R08, R09, R10, R12, R13. - -**작업 대상:** Core 진행/저장 경계, `UnityRuntime/Persistence/`, 관련 화면·검사. - -**동작 계약:** 퍼즐별 하나씩 저장하고 자유 놀이는 전체 하나만 남긴다. 완료 기록은 재플레이로 취소되지 않는다. 공급·묶음 반납은 자유 놀이에서만 가능하며 0개 상태를 복구할 수 있다. 조작/카메라 확정·화면 이동·앱 일시 정지에서 저장한다. 성공 관련 상태는 일관되게 교체한다. 유효본 복구·손상 파일 보존·실패 기록을 제공한다. - -**검증:** 두 미완료 퍼즐과 자유 놀이를 번갈아 저장·복원하고 ID·위치·소속·계수·카메라를 비교한다. 자동 저장 검사에는 N≥2에서 일부 구만 센 미완료 상태도 포함한다. 저장 중단·손상·자유 놀이 교체·재플레이·시범 기록을 검사한다. Persistence 단계의 Android 재시작·일시 정지 사례도 통과한다. - -**공유 작성:** JSON 필드나 버전을 임의로 바꾸지 않는다. 같은 씬·저장 연결은 실행 담당자가 한 번 갱신한다. - -### T6 — 30개 스테이지·도움·학습 안내 - -**목표:** R07 전체 정의와 실제 순차 해금·재플레이·도움·6/10번 시범을 완성한다. - -**근거:** R05, R06, R07, R08, R09, R10, R11, R12, R13, R14. - -**작업 대상:** StageAssets의 30개 자산·놀이터 배치, Core의 정의 검증·진행·힌트, 안내 화면, 검사. - -**동작 계약:** 정의와 순서는 R07과 전부 일치한다. 수 세기는 정확히 N개를 제공하며 뒤·위에 있는 구도 찾고 잡을 수 있게 한다. 모든 퍼즐은 최종 조건으로 판정한다. 잠금/완료/이어하기를 표시한다. 시범은 첫 지정 시점에 한 번만 보이며 퍼즐을 대신 풀지 않는다. 도움은 현재 가능한 대상/첫 행동을 강조한다. 30번 후에는 존재하지 않는 다음 번호를 열지 않는다. - -**검증:** 30개 자산의 번호·총량·목표·종류 검증, 실제 Core 호출로 전체 해답 경로, 새 저장에서 30번까지 해금, 다른 풀이·힌트 유효성·시범 재진입을 확인한다. 에디터에서 각 놀이터의 발견·조작 가능성을 플레이한다. Content 전체 EditMode·PlayMode와 변경된 진행 흐름의 Android 검사를 수행한다. - -**공유 작성:** 번호마다 별도 씬·규칙을 만들지 않는다. 초기 배치와 장식은 자산·프리팹으로 관리한다. - -Blender 소품과 공유 프리팹으로 세 테마의 입체 세트를 구성하여 고정 배치 참조에 연결한다. 테마별 발견·조작 가능성과 성공 후 동일 공간·복원된 테마를 확인한다. 실제 시범·현재 상태별 도움 문구를 Qwen 카탈로그에 빠짐없이 기록해 생성·검수하고, 최종 파일 교체에 필요한 전체 목록을 확정한다. - -### T7 — 카툰 표현·한국어 음성·모바일 화면 - -**목표:** 검증된 기능을 아이가 읽고 만질 수 있는 최종 외관과 소리로 표현한다. - -**근거:** R01, R02, R03, R04, R05, R06, R08, R09, R10, R11, R12, R13, R14. - -**작업 대상:** Presentation의 캐릭터·UI·세 테마 놀이터·효과·한국어 음성·효과음·음악·글꼴, SourceAssets 제작 원본, 음원 요청/대본표·자산 출처와 사용 고지 기록, PlayMode 검사. - -**동작 계약:** 묶음마다 얼굴·숫자 하나, 구별 선택/계수 강조, 부드러운 오답 반응, 성공 후 같은 공간을 제공한다. 가로 스마트폰·태블릿 비율과 안전 영역을 확인한다. 회전해도 숫자가 읽혀야 한다. 장식으로 구를 찾거나 잡을 수 없게 만들지 않는다. 필요한 한국어 숫자 읽기·안내·효과음을 허가된 실제 파일로 포함하고 오프라인 재생한다. - -R11의 합체 영웅 외형과 변신 연출을 구현한다. 대표 얼굴과 얇은 마스크, 접촉부의 빛, 묶음마다 하나인 자력 망토로 합체·분리를 표현한다. 아이가 만든 기본 배치를 유지하며 각 구를 셀 수 있게 한다. 2+3의 합체와 5에서 떨어진 두 구를 고른 분리를 대표 연출로 제작하고, 1~10 및 임의 배열에도 적용한다. 연출의 취소·재진입·복원은 현재 확정 상태에 맞춰 처리한다. - -**검증:** 얼굴·숫자·소속 일치, 시범·도움·오답·전환, 한글 누락·잘림·소리 누락, 입력 차단, 10개 한도·마지막 번호·복원 화면을 확인한다. 출처·사용 범위를 기록하며 임시 음성 또는 텍스트만으로 음성 완성을 선언하지 않는다. 전체 EditMode·PlayMode를 회귀 실행한다. - -합체 전 두 얼굴·두 망토가 확정 후 각각 하나가 되고, 분리 후 두 묶음에 각각 하나씩 생기는지 검사한다. 선택만 한 상태·무효 분리·취소에서 묶음 구성과 얼굴·숫자·망토 개수가 바뀌지 않는지 확인한다. 연속 합체·분리와 연출 중 화면 이동·앱 복원 후 구 ID·계수·기본 배치가 유지되고 중복 얼굴·망토가 남지 않아야 한다. 직선·평면·입체 배열과 서로 다른 두 가지 5인 친구를 회전하며 구 경계·숫자·선택·계수의 가독성을 수동 확인한다. 이 검사를 장식의 픽셀 모양을 고정하는 자동 테스트로 대체하지 않는다. - -**공유 작성:** 화면 작업에서 Core 규칙·저장 형식을 바꾸지 않는다. 실행 담당자가 최종 프리팹과 씬 연결을 갱신한다. - -앞의 자산 제작 절차를 따라 AI 2D 그림·Blender FBX·URP 재질·망토/VFX와 Qwen3-TTS WAV의 최종 파일·출처 기록을 확인한다. 현재 카탈로그·고정 프로필의 전체 필수 음성에 대한 생성/재사용 이력과 파일 검사, 사용자 청취 승인 44개의 정확한 채택 해시를 유지한다. 필수 안내와 하나~열의 실제 재생, 빠른 탭 순차 발화와 성공 후 마지막 숫자, 화면 전환/일시 정지/복원 정리를 확인한다. 효과음·음악은 첫 버전에서 제외하여 반복·믹싱 청취 인수는 요구하지 않는다. 최종 사용 파일의 출처·조건·필요 고지가 누락되거나 필수 음성의 생성/청각 검수가 미완료이면 해당 항목을 통과로 표시하지 않는다. - -### T8 — 최종 Android 전체 검증과 결과물 - -**목표:** 완성한 30개 체험판의 APK와 검증 증거를 제공한다. - -**근거:** R01, R03, R07, R08, R09, R10, R11, R12, R13, R14. - -**작업 대상:** 빌드/검증 진입점의 Final 범위, 최종 APK·자동 검사·Android 증거 경로. - -**동작 계약:** 전체 EditMode·PlayMode를 실행하고 최종 자산을 포함한 APK를 빌드한다. AVD에서 설치·실행·수집·합체·분리·도움·시범·순차 해금·자유 놀이·오프라인 소리를 확인한다. 아래 대표 흐름과 두 미완료 퍼즐/자유 놀이 독립 복원을 재현한다. 첫 단계 증거와 최종 증거를 구분한다. - -**검증:** Final 네 검사 단계, 실제 검사 수·실패/누락 여부, APK·해시·설정·로그·화면 기록을 확인한다. 결과를 통과/실패/미실행으로 구분하고 미실행이 있으면 전체 검증 완료를 선언하지 않는다. 실제 기기와 iOS·스토어 배포는 이번 범위가 아니다. - -최종 파일·사용 고지가 포함된 APK에서 세 테마와 한국어 음성을 확인한다. 효과음·음악은 첫 버전에 포함하지 않는다. TTS 모델/서버/제작 도구 없이 오프라인 재생되며 빠른 계수·마지막 숫자·전환 정리가 유지되어야 한다. 에디터에서만 재생한 파일이나 다른 APK의 증거로 최종 앱의 음성 검사를 대체하지 않는다. - -**공유 작성:** 최종 설정은 단독으로 갱신한다. 이때 기능을 고치면 관련 회귀 검사와 최종 APK/증거를 다시 만든다. - -## 9. 단계별 장면 연결과 대표 플레이 - -장면 연결은 별도 작업 번호가 아닌 실행 담당자의 단계별 작업이다. T2 이후 각 단계의 프리팹·상태·입력·저장을 연결하고 검사한 뒤 다음 단계로 진행한다. 최종 T7까지 장면 연결을 미루지 않는다. - -| 대표 흐름 | 실제 화면에서 확인할 결과 | -|---|---| -| 1번 | 직접 끌어 수집 후 한 번 눌러야 성공 | -| 6번 | 1과 1을 붙여 2; 최초 시범은 다음 진입에 자동 반복 안 됨 | -| 9번 | 2와 3을 합쳐 5; 구 다섯 알과 합체 후 얼굴·숫자·자력 망토 각각 하나 확인 | -| 10번 | 나누기·선택·끌기로 2를 1과 1로 분리; 선택만으로 성공 안 됨 | -| 13번 | 5에서 두 알을 선택해 2와 3으로 분리; 선택 중 강조와 확정 후 각 팀의 얼굴·숫자·망토 확인 | -| 14번 | 3과 1을 2와 2로; 다른 풀이 순서도 인정 | -| 20번 | 시점을 돌려 뒤쪽 구를 찾고 10개 수집·계수 전에는 완료 안 됨 | -| 30번 | 6과 4를 5와 5로 만들고 자유 놀이; 없는 다음 번호 표시 안 됨 | -| 저장 | 두 미완료 퍼즐과 최근 자유 놀이의 ID·배치·계수·카메라 독립 복원; N≥2에서 일부 구만 센 상태도 재실행 후 유지 | -| 자유 놀이 | 10개에서 추가 거부, 묶음 반납 후 추가, 빈 상태에서 다시 꺼내기 | - -정상 순차 플레이 검사는 해금을 우회하지 않는다. 특정 경계의 자동 검사에서 상태를 준비하는 것과 실제 제품에 번호 우회 기능을 제공하는 것은 구분한다. 30개 자료·해답 검사는 위 대표 플레이만으로 대체하지 않는다. - -## 10. 코드·자산 변경 규칙 - -- 네임스페이스는 `MagnetismHeroes` 아래 책임별로 나눈다. 타입·메서드·공개 속성은 PascalCase, 지역 변수는 camelCase, 비공개 필드는 _camelCase로 작성한다. -- Inspector 참조는 비공개 직렬화 필드로 드러내고 필수 참조 누락을 초기화·검사에서 발견한다. 고정 정의와 상태를 중복 소유하지 않는다. -- 데이터와 규칙의 변경 경로를 좁게 유지하고 정상적인 무효 조작은 거부 결과로 처리한다. 불가능한 상황을 위한 방어 계층을 여러 겹 만들지 않는다. -- `.meta`와 실제 소스·자산·프로젝트 설정은 함께 관리하고 생성 캐시·APK·검사 로그는 소스로 취급하지 않는다. 기존 Git 파일 변경과 커밋은 개발 실행 단계에서 담당한다. -- 기능 변경마다 영향을 받은 요구사항과 검증 결과를 남긴다. 관련 없는 리팩터링·프레임워크 추가·폴더 개편은 하지 않는다. -- 패키지 목록·어셈블리·씬·시작 연결·빌드 설정은 실행 담당자가 단독으로 갱신한다. 기능별 작업 대상과 겹치는 변경은 병합 전에 확인한다. - -## 11. 요구사항 대응과 실행 그래프 - -| 요구사항 | 구현·검증 작업 | -|---|---| -| R01 | T2, T3, T7, T8 | -| R02 | T1, T2, T4, T7 | -| R03 | T2, T3, T4, T7, T8 | -| R04 | T1, T4, T7 | -| R05 | T1, T2, T3, T6, T7 | -| R06 | T1, T4, T6, T7 | -| R07 | T4, T6, T8 | -| R08 | T2, T5, T6, T7, T8 | -| R09 | T1, T2, T3, T4, T5, T6, T7, T8 | -| R10 | T2, T3, T5, T6, T7, T8 | -| R11 | T2, T3, T4, T6, T7, T8 | -| R12 | T1, T2, T3, T4, T5, T6, T7, T8 | -| R13 | T1, T2, T3, T4, T5, T6, T7, T8 | -| R14 | T2, T3, T4, T6, T7, T8 | - -T2는 T1 규칙·데이터를 소비한다. T3는 T2의 실제 플레이·저장을 Android에서 검증한다. T4는 기존 플레이 경로를 확장하며 T3 뒤에 두는 것은 합의한 조기 플랫폼 검사 순서다. T5는 T4에서 확장한 묶음·조작 상태를 저장한다. T6는 T5의 진행·저장·자유 놀이 전체 수명을 사용한다. T7은 T6의 모든 목표·안내·진행 상태를 표현하고 T8은 최종 표현과 자산을 포함한 전체 앱을 검증한다. - -현재는 같은 플레이 흐름을 단계마다 확장하므로 기능 단계를 순차 실행한다. 초기화와 단계별 공통 연결은 별도 그래프 작업으로 만들지 않는다. Qwen 음성 생성·부분 재생성은 T2·T4·T6·T7 내부의 제작 작업이며 후속 작업에는 검수된 파일과 기록을 전달한다. 작업 사이에 별도 일괄 재생성을 요구하는 단계는 없으므로 재생성 장벽은 두지 않는다. ScriptableObject의 일반 Inspector 편집·자산 검사도 코드 생성 장벽이 아니다. - -```yaml -tasks: - - id: T1 - depends: [] - risk: RISKY - - id: T2 - depends: [T1] - risk: RISKY - - id: T3 - depends: [T2] - risk: RISKY - - id: T4 - depends: [T3] - risk: RISKY - - id: T5 - depends: [T4] - risk: RISKY - - id: T6 - depends: [T5] - risk: RISKY - - id: T7 - depends: [T6] - risk: RISKY - - id: T8 - depends: [T7] - risk: RISKY -regen_barriers: [] -``` - -## 참고한 공식 기술 문서 - -위 선택은 이 프로젝트의 규모와 합의한 Unity 제작 흐름에 맞춘 것이다. UI Toolkit도 현행 Unity에서 3D UI를 지원한다. uGUI 선택은 프리팹·MonoBehaviour 연결과 애니메이션 제작 흐름을 우선한 것이며 UI Toolkit의 기능 부재를 전제로 하지 않는다. - -- [Unity URP 소개](https://docs.unity3d.com/6000.6/Documentation/Manual/urp/urp-introduction.html): 모바일을 포함한 렌더링 구성. -- [Unity UI 비교](https://docs.unity3d.com/6000.6/Documentation/Manual/UI-system-compare.html): uGUI·UI Toolkit 제작 방식과 지원 범위. -- [Input System 터치](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.14/manual/Touch.html): EnhancedTouch와 포인터 기반 터치 시뮬레이션. 실제 패키지 버전은 초기화 시 에디터 호환 버전으로 고정한다. -- [Unity Test Framework 검사 구분](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html): EditMode는 에디터에서, PlayMode는 에디터 또는 Player에서 실행 가능하다. -- [ScriptableObject](https://docs.unity3d.com/6000.6/Documentation/Manual/class-ScriptableObject.html), [어셈블리 구성](https://docs.unity3d.com/6000.6/Documentation/Manual/assembly-definition-files.html): 고정 자산 제작과 코드 참조 경계. -- [Unity 3D 파일 형식](https://docs.unity3d.com/6000.6/Documentation/Manual/3D-formats.html): 제작 원본을 별도로 두고 FBX 등 내보낸 파일을 가져오는 흐름. -- [AudioClip 가져오기](https://docs.unity3d.com/6000.6/Documentation/Manual/class-AudioClip.html), [Audio Mixer](https://docs.unity3d.com/6000.6/Documentation/Manual/AudioMixer.html): 클립별 메모리·재생 설정과 음성/효과음/음악 혼합. -- [TrailRenderer](https://docs.unity3d.com/6000.6/Documentation/Manual/class-TrailRenderer.html): 이동 흔적은 보조 표현으로 사용하고 정지 시 망토는 별도 리본 메시로 유지한다. -- [Qwen3-TTS 공식 코드·사용법](https://github.com/QwenLM/Qwen3-TTS), [선택한 1.7B CustomVoice 모델](https://huggingface.co/Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice): 한국어·Sohee·CustomVoice 생성과 모델 파일/라이선스 확인. 설치된 코드의 API와도 대조한다. -- [PyTorch 공식 설치 안내](https://pytorch.org/get-started/locally/): 개발 실행에서 기존 환경의 CPU 구성과 호환되는 Windows CUDA 구성을 확인한다. 특정 CUDA wheel 버전은 실제 호환성 검증 후 기록한다. diff --git a/.dryforge/001/spec.md b/.dryforge/001/spec.md deleted file mode 100644 index c1595e1..0000000 --- a/.dryforge/001/spec.md +++ /dev/null @@ -1,277 +0,0 @@ -# MagnetismHeroes 첫 체험판 기획서 - -2026-10-06 사용자 결정: 첫 버전은 효과음·배경음악 없이 진행한다. 한국어 숫자·안내 음성 44개와 시각 연출은 유지한다. 아래 효과음·음악 제작·포함·반복·믹싱 기준은 첫 버전의 필수 인수 범위에서 제외하며, 무음 효과음·음악을 미완료로 취급하지 않는다. 이후 추가는 별도 요청으로 진행한다. 음성 순서·오프라인 재생·전환 정리 및 출처·고지 기준은 계속 적용한다. - -이 문서는 구현할 게임의 동작과 완료 기준을 정의한다. 대상은 한국어를 사용하는 5~7세이며, 첫 제작 대상은 안드로이드 스마트폰·태블릿이다. 숫자를 이루는 자석을 직접 찾고, 세고, 붙이고, 떼면서 수량과 숫자 기호를 연결한다. - -## R01. 제작 범위와 학습 목표 - -- Unity 6 기반의 가로 화면 3D 카툰 게임을 만든다. 설치된 에디터 버전은 `6000.6.3f1`이다. -- 첫 체험판에는 아래에 정의한 30개 스테이지, 순차 해금, 재플레이, 도움, 성공 후 자유 놀이, 기기 내 저장을 포함한다. -- 학습 범위는 1~10 수 세기, 합치기, 분리하기, 두 행동을 함께 쓰기다. 분리는 한 수를 두 수로 구성하는 경험이며, 이번에는 나눗셈·곱셈 교육으로 확장하지 않는다. -- 혼자 오프라인으로 플레이한다. 계정, 서버, 광고, 결제, 사용자 추적을 추가하지 않는다. -- 아이폰·아이패드 지원은 후속 범위다. 이번에 iOS 빌드나 검증을 완료했다고 주장하지 않는다. 앱 스토어 배포도 이번 작업에 포함하지 않는다. -- Numberblocks는 수량을 캐릭터와 연결하는 표현의 참고다. 게임의 캐릭터·배경·자석 표현은 이 프로젝트에 맞게 만든다. - -## R02. 자석과 친구의 규칙 - -- 구 형태의 자석 한 개는 항상 1이다. 묶음의 값은 소속 구의 개수와 같다. -- 자석을 합쳐도 각각의 구가 보인다. 큰 구 하나로 대체하거나 구의 크기로 수량을 표현하지 않는다. -- 묶음마다 대표 얼굴 하나와 숫자 표시 하나가 있다. 서로 떨어진 1 두 개에는 각각 얼굴과 숫자 1이 있고, 붙이면 하나의 얼굴과 숫자 2를 가진 친구가 된다. -- 숫자는 묶음 위에 표시하며 카메라를 돌려도 읽을 수 있게 한다. 자석 배열로 숫자 글자의 모양을 만드는 기능은 포함하지 않는다. -- 같은 개수는 어떤 모양에서도 같은 수다. 직선·평면·입체 배열에 따라 정답 여부가 달라지지 않는다. -- 아이가 붙인 모양이 합체 영웅의 몸이 된다. 변신 연출 때문에 정해진 캐릭터 형태로 재배열하지 않는다. 분리 시 두 친구로 모이는 배치 조정은 R04를 따른다. -- 자석은 중력 없이 놓은 위치에 뜬다. 다른 묶음 가까이에 가져가 놓으면 접촉하는 위치에 붙고 하나의 묶음이 된다. 합체 직전에는 붙을 대상을 알아볼 수 있게 한다. -- 합체는 두 묶음의 구를 모두 보존한다. 분리는 기존 구를 두 묶음으로 나누며 구를 생성하거나 없애지 않는다. 한 구는 정확히 한 묶음에 속한다. -- 위치·모양 변경은 구의 정체성을 바꾸지 않는다. 이 원칙으로 중복 계수를 방지하고 저장 후 같은 놀이 상태를 복원한다. - -## R03. 입체 공간과 터치 조작 - -| 상황 | 입력 | 결과 | -|---|---|---| -| 일반 상태 | 친구를 한 손가락으로 끌기 | 그 묶음 전체 이동 | -| 나누기 상태 | 구를 짧게 누르기 | 해당 구 선택 또는 선택 해제 | -| 나누기 상태 | 선택한 구를 끌어내기 | R04에 따라 분리 | -| 빈 공간 | 한 손가락으로 쓸기 | 놀이터를 보는 시점 회전 | -| 놀이터 | 두 손가락으로 오므리거나 벌리기 | 확대·축소 | -| 수 세기 상태 | 구를 짧게 누르기 | R05에 따라 한 번 세기 | - -- 앞뒤 위치는 시점을 옆으로 돌린 후 끌어 옮기는 방식으로 조절한다. 평면에만 놓거나 정해진 방향으로만 붙도록 제한하지 않는다. -- 놀이터는 한 화면에서 둘러보는 작은 입체 공간이다. 플레이어 캐릭터를 걸어 다니게 하는 넓은 탐험 월드는 만들지 않는다. -- 회전하여 사물 뒤·위·아래를 확인할 수 있고, 각 수집 자석에는 발견하고 잡을 수 있는 시점이 존재해야 한다. 장식물 내부에 완전히 갇힌 자석을 배치하지 않는다. -- 구를 눌렀다가 끄는 동작은 탭과 구분한다. 끌기를 계수나 선택 탭으로 중복 처리하지 않는다. -- 화면 버튼 위에서 시작한 입력은 친구나 카메라를 움직이지 않는다. 두 손가락 조작을 친구 이동·분리로 동시에 처리하지 않는다. -- 화면을 벗어나 놓거나 조작이 취소되면 자석을 잃지 않도록 마지막 유효 위치를 유지한다. 카메라에는 놀이 대상이 복구 불가능하게 사라지지 않는 거리 경계를 둔다. -- 터치 판정 여유, 흡착 거리, 회전·확대 속도, 애니메이션 시간은 실제 조작으로 조정할 값이다. 수량·정답 규칙을 바꾸는 수단으로 사용하지 않는다. - -## R04. 나누기 - -1. 아이가 친구를 선택하고 ‘나누기’를 누른다. -2. 같은 친구에 속한 구들을 눌러 선택한다. 선택된 구를 빛으로 구분하고 선택 개수를 표시한다. -3. 선택한 구를 끌어내면 선택한 쪽과 남은 쪽이 각각 하나의 친구로 자연스럽게 모인다. - -- 서로 떨어진 구도 함께 선택할 수 있다. 선택 구끼리 물리적으로 붙어 있을 필요는 없다. -- 두 결과 묶음은 각각 숫자와 대표 얼굴을 가진다. 기존 모양은 분리 과정에서 일부 달라질 수 있다. -- N개 친구에서 유효한 선택 수는 1~N-1이다. 0개·전체 선택은 분리를 실행하지 않으며 구를 소실시키지 않는다. 1인 친구는 나눌 수 없다. -- 선택 중에는 한 묶음만 다룬다. 다른 묶음으로 대상을 바꾸거나 나누기를 취소하면 확정하지 않은 선택을 해제한다. -- 분리 후 선택 상태를 종료한다. 떨어진 두 친구는 아이가 다시 가까이 가져오기 전까지 즉시 재합체하지 않는다. -- 예: 5에서 양 끝의 두 개를 선택하면 결과는 항상 2와 3이다. 중간 연결이 끊겼다는 이유로 세 개 이상의 친구가 생기지 않는다. - -## R05. 탐험하며 수 세기 - -- 해당 스테이지의 목표 N과 정확히 N개의 자석을 제공한다. 각 자석은 처음에는 분리된 1인 친구로 놀이터 곳곳에 있다. -- 아이는 시점을 돌려 발견한 자석을 직접 끌어와 다른 자석에 붙인다. 누르기만 해도 자동 수집되는 방식은 사용하지 않는다. -- 모든 자석을 하나의 N인 친구로 모은 뒤 하나씩 눌러 센다. 1 스테이지에서도 직접 자석을 끌어 모으는 행동과 한 번 세는 행동이 필요하다. 처음부터 놓여 있다는 이유만으로 수집 완료 또는 성공으로 처리하지 않는다. -- 직접 끌어 놓은 구와 그 조작으로 합쳐진 묶음의 구들을 수집한 것으로 기록한다. 카메라를 돌리거나 자석을 보기만 한 것은 수집이 아니다. -- 세기 입력은 모든 구를 수집하여 하나로 모은 뒤 활성화된다. 처음 누른 구에만 계수 표시를 남기고, 이번에 센 개수에 맞춰 ‘하나, 둘, …, 열’을 들려준다. 이미 센 구를 다시 눌러도 수가 늘지 않는다. -- 뒤쪽 구는 시점을 돌려 찾는다. 위치·모양 변경이나 합체·분리는 이미 센 구의 기록을 지우지 않는다. 분리하여 모으기 조건을 잃으면 다시 하나로 모은 후 세기를 계속한다. -- 성공 조건은 **전체 N개 수집 + N개의 단일 묶음 + N개 모두 계수 완료**다. 숫자 표시를 읽거나 묶음을 만들기만 해서는 성공하지 않는다. -- 완료 전에는 자석을 추가하거나 반납할 수 없다. 총 N개를 유지한다. - -## R06. 합치기·분리하기 퍼즐 - -- 탐험 없이 표에 지정된 묶음들이 준비된 상태로 시작한다. 목표는 만들 친구들의 숫자로 보여준다. -- 놀이 공간에 있는 모든 묶음의 개수를 목표 구성과 비교한다. 순서와 위치는 무관하지만 묶음 수와 각 개수는 같아야 한다. -- 예: 목표가 2와 2이면 4 하나 또는 2·1·1은 오답이다. 2인 친구 두 개를 만들면 성공이다. -- ‘하나 옮기기’ 등 스테이지의 설명은 풀이를 돕는 표현이다. 최종 구성에 도달하면 모두 합친 뒤 다시 나누는 풀이도 인정한다. 특정 제스처 이력이나 최소 횟수를 성공 조건에 넣지 않는다. -- 조작을 확정한 후 정답을 판정한다. 합체·분리 애니메이션의 중간 모양으로 정답을 판정하지 않는다. -- 목표와 다르면 친구가 부담 없는 표정으로 반응하며 계속 조작할 수 있다. 시간제한, 목숨, 실패 점수, 오답에 따른 초기화는 없다. -- 완료 전 자석 공급·반납은 불가능하다. 모든 계산 퍼즐은 자석 총량을 보존한다. - -## R07. 30개 스테이지 - -표의 `수집`은 처음에 흩어진 1인 친구들이다. `구성`은 준비된 묶음을 조작하는 퍼즐이다. 같은 숫자가 반복되면 서로 다른 묶음이다. - -| 번호 | 종류 | 처음의 구 수 또는 묶음 | 목표 | 경험 | -|---:|---|---|---|---| -| 1 | 수집 | 1개 | 1개 모아 세기 | 하나 | -| 2 | 수집 | 2개 | 2개 모아 세기 | 둘 | -| 3 | 수집 | 3개 | 3개 모아 세기 | 셋 | -| 4 | 수집 | 4개 | 4개 모아 세기 | 넷 | -| 5 | 수집 | 5개 | 5개 모아 세기 | 다섯 | -| 6 | 구성 | 1, 1 | 2 | 처음 합치기 | -| 7 | 구성 | 2, 1 | 3 | 하나 더 붙이기 | -| 8 | 구성 | 3, 1 | 4 | 하나 더 붙이기 반복 | -| 9 | 구성 | 2, 3 | 5 | 두 묶음 합치기 | -| 10 | 구성 | 2 | 1, 1 | 처음 나누기 | -| 11 | 구성 | 3 | 2, 1 | 하나 떼기 | -| 12 | 구성 | 4 | 2, 2 | 두 개 함께 떼기 | -| 13 | 구성 | 5 | 3, 2 | 다른 크기로 나누기 | -| 14 | 구성 | 3, 1 | 2, 2 | 나누고 다시 합치기 | -| 15 | 구성 | 4, 1 | 3, 2 | 두 행동 함께 쓰기 | -| 16 | 수집 | 6개 | 6개 모아 세기 | 여섯 | -| 17 | 수집 | 7개 | 7개 모아 세기 | 일곱 | -| 18 | 수집 | 8개 | 8개 모아 세기 | 여덟 | -| 19 | 수집 | 9개 | 9개 모아 세기 | 아홉 | -| 20 | 수집 | 10개 | 10개 모아 세기 | 열 | -| 21 | 구성 | 5, 1 | 6 | 큰 수에 하나 붙이기 | -| 22 | 구성 | 3, 4 | 7 | 다른 두 묶음 합치기 | -| 23 | 구성 | 4, 4 | 8 | 같은 크기 합치기 | -| 24 | 구성 | 5, 5 | 10 | 열 만들기 | -| 25 | 구성 | 6 | 5, 1 | 큰 수에서 하나 떼기 | -| 26 | 구성 | 7 | 4, 3 | 여러 개 골라 떼기 | -| 27 | 구성 | 9 | 5, 4 | 아홉 나누기 | -| 28 | 구성 | 10 | 5, 5 | 열을 같은 크기로 | -| 29 | 구성 | 5, 3 | 4, 4 | 두 친구의 크기 바꾸기 | -| 30 | 구성 | 6, 4 | 5, 5 | 종합 놀이 | - -앞의 구성 스테이지는 총량 5 이하, 뒤의 구성 스테이지는 총량 6~10이다. 뒤 구간에서도 1~5인 친구를 재료나 결과로 사용한다. - -## R08. 진행·도움·학습 안내 - -- 새 저장에서는 1번만 열린다. 스테이지를 성공하면 완료 기록을 남기고 다음 번호를 연다. 완료한 스테이지는 다시 플레이할 수 있다. -- 재플레이에서 풀다가 나와도 이전 완료 기록과 뒤 스테이지의 해금은 유지한다. -- 성공하면 같은 공간과 자석 배치를 유지한 채 축하하고 자유 놀이로 전환한다. 자동으로 다음 스테이지에 이동하지 않는다. -- 자유 놀이에서 ‘다음’을 누르면 다음 스테이지의 저장 상태가 있을 경우 이어 하고, 없으면 처음 상태로 시작한다. 30번에는 존재하지 않는 31번으로 가는 버튼을 제공하지 않는다. 자유 놀이와 스테이지 선택은 계속 사용할 수 있다. -- 6번 첫 진입에서 합치기, 10번 첫 진입에서 ‘나누기 → 선택 → 끌어내기’를 짧게 시범으로 안내한다. 시범은 퍼즐의 실제 구를 조작하거나 완료 기록을 만들지 않는다. 이미 본 시범은 자동 반복하지 않는다. -- 도움 버튼은 현재 상태에 맞는 한 행동을 제안한다. 수집 중에는 아직 모으지 않은 자석 하나, 세는 중에는 아직 세지 않은 구 하나를 알려준다. -- 수집한 구들을 다시 분리하여 여러 친구가 된 경우에는 먼저 합칠 두 묶음을 알려준다. 수집 기록이 모두 있다는 이유로 세기 조건이 아직 안 되는 상태에서 계수 힌트만 제시하지 않는다. -- 구성 퍼즐의 도움은 현재 묶음에서 목표에 접근할 수 있는 첫 행동을 제안한다. 이전 배치에만 유효했던 고정 힌트를 반복하지 않는다. 예를 들어 적절한 묶음을 가리키며 ‘4에서 하나를 떼어 볼까?’라고 안내한다. -- 도움은 자석을 대신 움직이거나 정답을 자동 실행하지 않는다. 도움 사용 횟수로 보상을 깎거나 진행을 막지 않는다. - -## R09. 성공 후 자유 놀이 - -- 스테이지를 성공한 자리에서 같은 친구들로 자유롭게 합체·분리·이동·입체 배열을 만들 수 있다. -- 자석을 하나씩 꺼낼 수 있고, 친구를 반납할 수 있다. 묶음에서 일부만 반납하려면 먼저 나누기로 분리한다. -- 화면에 존재하는 구의 합계는 최대 10이다. 10개이면 추가를 실행하지 않고 구를 중복 생성하지 않는다. 반납하면 그 수만큼 다시 꺼낼 수 있다. -- 전부 반납한 빈 공간도 허용한다. 이는 0을 가르치는 별도 스테이지가 아니며, 자석을 다시 꺼낼 수 있다. -- 자유 놀이에는 정답·실패가 없고 완료 기록을 취소하지 않는다. -- 최근 자유 놀이 상태 하나만 보존한다. 다른 스테이지에서 자유 놀이를 시작하면 이 저장은 새 놀이 상태로 교체된다. 스테이지별 미완료 퍼즐 저장과는 별개다. - -## R10. 시작 화면과 저장·이어하기 - -- 시작 화면에서 스테이지 선택과, 저장이 있을 때 최근 자유 놀이 이어 하기를 제공한다. -- 스테이지별 미완료 퍼즐 상태를 각각 하나씩 저장한다. 다른 스테이지로 이동해도 이전 스테이지의 배치를 덮어쓰지 않는다. -- 저장이 있는 스테이지를 선택하면 해당 퍼즐을 이어 한다. 저장이 없는 완료 스테이지를 선택하면 처음 배치로 재플레이한다. -- 성공한 퍼즐의 미완료 저장은 제거하고, 완료 기록과 최근 자유 놀이 상태를 함께 갱신한다. 재플레이 중 미완료 저장과 과거 완료 기록은 동시에 존재할 수 있다. -- 복원 대상은 구의 위치, 묶음 소속, 수집·계수 여부, 바라보던 방향과 확대 정도다. 튜토리얼을 본 기록도 유지한다. -- 자석을 놓거나 합체·분리·계수·추가·반납을 확정했을 때, 카메라 조작을 마쳤을 때, 화면 전환·앱 일시 정지 시 저장한다. 손가락을 대고 있는 중간 위치, 미확정 분리 선택, 애니메이션 중간 프레임은 저장 대상이 아니다. -- 중단 시에는 마지막으로 확정한 조작 상태로 돌아온다. 앱이 중간에 종료되어도 반쪽 합체, 구의 복제·소실, 완료만 기록되고 자유 놀이가 비는 상태가 생기지 않도록 저장을 한 단위로 교체한다. -- 기기 내 앱 전용 공간에 저장한다. 기기 간 동기화·여러 사용자 프로필·계정 복구는 범위 밖이다. - -## R11. 화면·캐릭터·한국어 소리 - -- 둥근 자석, 표정이 있는 대표 얼굴, 친근한 작은 입체 놀이터로 구성한다. 합체·분리 때 표정과 움직임으로 친구가 바뀌었음을 보여준다. -- 장식은 구·숫자·목표·선택 표시를 가리지 않는다. 탐험용 가림은 시점을 돌리면 해결할 수 있어야 한다. -- 숫자, 목표, 현재 선택 개수, 주요 버튼은 가로 스마트폰·태블릿 화면과 화면 가장자리 안전 영역에 맞춰 배치한다. 실제 기기의 가독성 합격은 후속 검증으로 구분한다. -- 한국어 화면, 하나~열 숫자 읽기와 짧은 안내 음성을 앱에 포함한다. 효과음·배경음악은 첫 버전에 포함하지 않는다. 음성은 네트워크 생성이나 다운로드가 없어도 재생되어야 한다. -- 소리만으로 조작·정답·힌트를 전달하지 않는다. 대응하는 숫자·강조·표정을 함께 보여준다. -- 원본 애니메이션의 캐릭터·음원을 제품 자산으로 가져오지 않는다. 사용할 자산의 출처와 사용 가능 범위를 기록한다. - -### 합체 영웅의 외형 - -시각적 정체성은 **살아 있는 자석 구 + 빛나는 연결부 + 자력 망토**다. 붙으면 하나의 영웅 팀이 되고, 나뉘면 함께 돕는 두 팀이 된다는 느낌을 표정과 움직임으로 전달한다. - -- 모든 구의 기본 크기는 같다. 매끈하고 둥근 장난감 질감으로 표현하며 각 구의 경계를 구분할 수 있어야 한다. 갑옷이나 몸체 장식으로 구를 덮거나 별도의 머리 구를 추가하지 않는다. -- 대표 얼굴은 기존 구 하나의 표면에 큰 눈·입과 얇은 영웅 마스크로 표현한다. 묶음 위 숫자와 함께 합체·분리 결과에 맞춰 갱신한다. -- 구가 접촉한 부분에는 부드러운 빛의 고리를 표현한다. 연결이 확정되면 붙은 자리에서 묶음 안으로 빛이 퍼져 자력으로 한 팀이 되었음을 보여준다. -- 자력 망토는 묶음 뒤에서 흐르는 얇은 빛의 리본이다. 안정된 상태에서 묶음마다 하나이며 이동·합체·분리에 반응한다. 얼굴·마스크·연결부·망토는 수량에 포함되지 않는 장식이며 독립적인 입력 대상이 아니다. -- 장식은 숫자와 구별 선택·계수 표시를 가리지 않는다. 입체 배치로 가려진 구는 카메라를 돌려 확인할 수 있어야 한다. 선택과 이미 센 구의 표시는 연결부의 장식 빛과 구분한다. -- 같은 수의 다른 배열은 동등한 수량 조건을 만족한다. 모양에 따른 자세·망토 움직임의 차이는 개성 표현이며 능력 등급이나 새로운 정답 조건이 아니다. 1~10 전체에 같은 외형 원칙을 적용한다. -- 시안의 색 조합·마스크 윤곽은 탐색안이다. 최종 색·빛의 강도·망토 길이·동작 시간은 모바일 가독성과 조작감을 보고 조정한다. - -### 합체·분리의 변신 연출 - -| 단계 | 표현 | 동작과의 관계 | -|---|---|---| -| 합체 후보에 접근 | 두 친구가 서로를 바라보고 구와 망토 끝이 살짝 끌리는 모습 | 붙을 대상을 알리는 미리보기; 구 소속은 아직 바꾸지 않음 | -| 합체 확정 | 접촉부에 빛의 고리, 연결된 구를 따라 빛이 퍼짐 | 확정된 합체 결과를 표현 | -| 한 팀 완성 | 대표 얼굴·숫자·망토가 각각 하나가 되고 짧게 떠오르며 웃음 | 아이가 만든 배치를 유지하고 새 수량 표시 | -| 분리 선택 | 선택 구의 테두리와 선택 개수 표시, 망토 일부가 선택한 쪽으로 흐름 | 선택만으로 두 친구를 만들지 않음 | -| 분리 확정 | ‘톡’ 효과와 함께 연결이 풀리고 양쪽에 얼굴·숫자·망토가 각각 하나씩 생김 | R04에 따른 두 결과 묶음을 표현 | -| 두 팀 완성 | 두 친구가 짧게 서로를 바라보고 고개를 끄덕이는 모습 | 같은 구들이 나뉘어 함께 일한다는 인상 | - -연출은 짧게 하고 반복 조작을 방해하는 긴 변신 장면을 넣지 않는다. 흔들림·살짝 떠오르기는 시각 표현이며 확정된 구 ID·수량·기본 배치·계수 기록을 바꾸지 않는다. 취소·다음 조작·화면 전환·앱 복원 시에는 현재 확정 상태에 맞춰 표현을 정리하고 이전 연출이 뒤늦게 얼굴·숫자를 덮어쓰지 않게 한다. 얼굴·연결부·망토는 복원 상태에서 다시 만들며 연출 중간 프레임은 저장하지 않는다. - -## R12. 구성과 데이터 계약 - -외부 API는 없다. 게임 규칙, 터치·공간 조작, 화면·소리 표현, 저장을 작은 책임으로 나눈다. 단일 플레이 세션에서 조작을 순서대로 확정하므로 여러 사용자의 동시 쓰기 조정은 필요하지 않다. 물리 연출의 흔들림이나 렌더링 프레임이 수학 상태를 결정하지 않는다. - -- 수량·구 소속·합체·분리·계수·목표 판정과 진행 제어는 Unity 객체에 의존하지 않는 순수 C#으로 구현한다. Unity 입력과 화면은 이 규칙을 호출하고 결과를 표현한다. 화면 객체의 직접 변경으로 수학 상태를 바꾸지 않는다. -- 렌더링은 URP, 입력은 Input System, 게임 UI는 uGUI와 TextMeshPro를 사용한다. 묶음의 숫자와 얼굴은 확정 상태에서 계산한다. -- 진행은 명시적인 상태 머신으로 제어하고, Presenter가 상태를 화면에 전달한다. 저장 인터페이스로 파일 접근을 분리하며 시작 코드에서 의존성을 직접 연결한다. 이 범위를 위해 범용 DI·이벤트·MVVM 프레임워크를 추가하지 않는다. -- 고정 스테이지는 ScriptableObject 자산으로 제작하고 실행 시 순수 C# 정의로 변환한다. 실행 중 원본 자산을 수정하지 않는다. 미완료 배치와 완료 기록은 별도의 로컬 JSON 버전 1에 저장한다. -- 개발은 작은 퍼즐의 입력·규칙·화면·저장을 함께 연결한 뒤 확장한다. 최초 수 세기 퍼즐부터 Android APK 설치·실행·이어하기를 검증하고, 전체 기능 완성 후 최종 Android 검증을 다시 수행한다. - -### 스테이지 정의 - -| 필드 | 의미와 제약 | -|---|---| -| `id` | 1~30의 고유 정수 | -| `kind` | `counting` 또는 `composition` | -| `initialGroups` | 처음 묶음별 양의 정수 배열. 수집형은 N개의 1 | -| `targetGroups` | 목표 묶음별 양의 정수 배열. 수집형은 `[N]` | -| `layout` | 구와 장식의 초기 배치. 모든 구를 발견·조작할 수 있어야 함 | - -초기 총량과 목표 총량은 같고 1~10이다. 표 R07이 정의의 기준이며 데이터 파일에서 임의로 숫자나 스테이지 수를 바꾸지 않는다. 목표 배열은 정렬하여 중복까지 포함해 비교한다. - -### 저장 계약 - -저장 형식은 로컬 JSON이며 다음 의미와 필드 경계를 보존한다. 직렬화 라이브러리의 내부 선택으로 게임 동작이나 저장 계약을 변경하지 않는다. - -| 필드 | 의미와 제약 | -|---|---| -| `schemaVersion` | 최초 버전 1 | -| `completedStageIds` | 중복 없는 완료 번호 목록. 정상 플레이에서 감소하지 않음 | -| `seenTutorialIds` | `merge`, `split` 중 이미 본 안내 목록 | -| `puzzleStates` | 스테이지별 미완료 상태 배열. 동일 번호 최대 하나, 총 최대 30개 | -| `latestFreePlay` | 없음 또는 최근 자유 놀이 상태 하나 | - -퍼즐 상태는 `stageId`, `balls`, `groups`, `collectedBallIds`, `countedBallIds`, `camera`를 가진다. 자유 놀이 상태는 `sourceStageId`, `balls`, `groups`, `camera`를 가진다. `sourceStageId`는 완료한 스테이지여야 한다. - -- `balls`: 상태 안에서 고유한 `id`와 3차원 `position`. 분리·합체·복원 후에도 구 ID는 유지한다. 자유 놀이에서 새로 꺼낸 구는 현재 상태와 충돌하지 않는 ID를 가진다. -- `groups`: 고유 `id`와 비어 있지 않은 `ballIds`. 모든 구는 하나의 묶음에 정확히 한 번 속하며, 존재하지 않는 구를 참조하지 않는다. -- `collectedBallIds`, `countedBallIds`: 존재하는 구 ID의 중복 없는 부분집합. 센 구는 수집한 구다. 구성 퍼즐에서는 두 목록이 비어 있다. -- `camera`: 유효한 시점 기준점 `target`, 회전 `rotation`, 양수 거리 `distance`. 값을 복원하여 같은 방향·확대 정도로 이어 한다. -- 얼굴·숫자는 묶음 소속에서 다시 계산한다. 중복 저장한 숫자 때문에 실제 개수와 표시가 달라지면 안 된다. -- 미완료 퍼즐에는 해당 스테이지 초기 총량과 동일한 구가 있어야 한다. 자유 놀이는 0~10개이며, 빈 상태이면 묶음도 비어 있다. -- 입력 선택과 진행 중 애니메이션은 저장하지 않는다. 수집·계수 단계는 복원된 상태에서 판정한다. -- 손상·불완전 저장은 구를 일부만 불러오지 않고 유효한 마지막 저장으로 복원한다. 유효본을 확인할 수 없으면 읽기 실패를 기록하고 정상 초기 상태로 진입하며, 손상 파일을 무조건 덮어써 원인을 없애지 않는다. - -### 상태 전이 - -- 화면은 시작 화면, 스테이지 선택, 퍼즐, 자유 놀이로 구분한다. -- 퍼즐 안에서는 일반 조작과 나누기 선택을 구분한다. 수집형의 세기 가능 여부는 R05 조건으로 결정한다. -- 성공 전이는 한 번만 확정한다. 성공 시 완료 기록·미완료 저장 제거·자유 놀이 생성은 같은 상태 변경이다. -- 다시 실행하면 시작 화면으로 들어가 아이가 스테이지 또는 최근 자유 놀이를 선택한다. 저장을 불러온 것 자체로 성공 연출을 반복하지 않는다. - -## R13. 필요한 검증과 결과물 - -현재 저장소에는 게임 코드와 기존 검증 명령이 없다. 개발 과정에서 자동 검증 진입점을 만들고 아래 증거를 남긴다. 이 기획 단계에서 게임 테스트나 빌드를 실행했다고 기록하지 않는다. - -| 검증 | 합격 기준 | -|---|---| -| 스테이지 자료 | 고유 번호 1~30, R07과 전부 일치, 총량 보존, 목표 구성 유효 | -| 수학 규칙 | 합체·분리 후 구 ID 보존, 모든 유효 분할, 0개·전체 선택 거부, 목표 다중집합 판정 | -| 수 세기 | 1번의 수집 생략 불가, 미수집 상태 계수 불가, 중복 탭 불가, N개 수집·단일 묶음·계수 모두 필요 | -| 조작·진행 | 이동/탭/회전/핀치 충돌 없음, 순차 해금·재플레이·도움·성공 후 자유 놀이, 30번 이후 경계 | -| 자유 놀이 | 10개 제한, 반납 후 추가, 빈 놀이판 복구, 완료 기록 불변 | -| 저장 | 최소 두 스테이지의 서로 다른 미완료 상태와 자유 놀이를 함께 저장·복원; 구·묶음·계수·카메라 일치; 성공 상태 변경의 일관성 | -| 전체 과정 | 새 저장에서 30개를 모두 해결할 수 있고 마지막까지 해금됨. 자동화 가능한 규칙은 자동 검사하고 화면 연결은 플레이 검사 | -| 구조와 데이터 경계 | 순수 C# 규칙에서 Unity 객체 참조 없음; 화면·저장 결과와 규칙 상태 일치; 플레이 전후 고정 스테이지 자산 불변 | -| 합체 영웅 표현 | 1~10에서 구 개수·기본 크기·배치 유지; 안정 상태의 묶음마다 얼굴·숫자·망토 각각 하나; 2+3→5와 5→2+3 표현 일치; 선택·계수 가독성; 취소·연속 조작·복원 후 오래된 연출 잔존 없음 | -| 자산과 소리 | 서로 다른 배경 테마 3개; 최종 모델·2D·시각 효과·TTS 음성 실제 포함; 출처·사용 조건 기록; 빠른 계수 입력의 순차 발화·마지막 숫자 보존; 화면 전환 후 음성 정리 확인; 효과음·배경음악 제외 | -| 조기 Android 검사 | 첫 수 세기 퍼즐의 APK 설치·실행, 수집·계수, 성공 전 배치의 종료·재실행 복원. 전체 30개 완료 검증으로 대신 기록하지 않음 | -| Android 빌드 | 설치 가능한 APK 생성, 빌드 로그와 사용한 에디터·도구·대상 설정 기록 | -| Android 가상 기기 | APK 설치·실행, 가로 화면, 오프라인 동작, 수집/합체/분리, 스테이지 진행, 앱 종료·재실행 후 개별 퍼즐과 자유 놀이 복원 확인 | - -- 결과물은 Unity 프로젝트, 안드로이드 APK, 자동 검사 결과, 가상 기기 검증 기록이다. -- 기능 구현 전에 해당 요구사항의 정상 동작·무효 동작·경계 조건을 합격 사례로 정한다. 필수 사례가 누락되거나 실패하면 그 기능을 완료로 표시하지 않는다. 필요한 검사 미실행은 통과와 구분하며, 실행한 소스 상태와 빌드에 검증 증거를 연결한다. -- 작업 환경의 Unity `6000.6.3f1`에는 Android Build Support와 Unity 제공 SDK·NDK·OpenJDK가 설치되어 있다. 설치 확인은 APK 빌드 성공과 별개이며, 빌드 결과로 개발 환경을 검증한다. -- 가상 기기 실행이 환경 문제로 불가능하면 실패 원인과 미검증 항목을 명시한다. APK가 생성되었다는 이유만으로 검증 전체를 완료 처리하지 않는다. -- 실제 손가락 조작, 스마트폰·태블릿별 가독성, 실제 기기의 성능·발열·배터리 확인은 기기 준비 후 수행한다. 가상 기기 결과로 이 항목들이 검증되었다고 주장하지 않는다. - -## R14. 자산 제작 범위와 인수 - -- 자석 친구·얼굴·마스크, 배경, 화면 그림, 연결 빛·자력 망토 등 특수 효과와 한국어 음성을 제작·확보하여 앱에 포함한다. 효과음·배경음악은 첫 버전에서 제외한다. 기존 수량·배치·조작·성공 규칙은 유지한다. -- 자석 표현과 프리팹·효과는 Unity에서 만들고, 입체 배경 소품은 Blender로 제작한다. AI 이미지는 실제 얼굴·UI 그림·텍스처에도 활용하며 스타일·투명 가장자리·사용 조건을 검수한다. 동적으로 바뀌는 숫자와 한국어 문구는 실제 텍스트로 표시한다. -- 배경은 서로 다른 테마 3개로 구성한다. 공통 소품을 재사용할 수 있지만 주요 소품·색·배경 표현으로 테마를 구별한다. 같은 테마의 배치를 여러 스테이지에서 사용하며 30개의 별도 배경 씬을 요구하지 않는다. 테마 이름·소품·색·스테이지별 테마 배정은 미술 조정 대상이고 R07의 순서·목표는 고정이다. -- 테마는 고정 스테이지 정의에서 가져온다. 성공 후 같은 공간을 유지하고 퍼즐·자유 놀이를 복원할 때 원래 스테이지의 테마를 사용한다. 배경 변경이 새로운 성공 조건이 되지 않는다. -- 효과음·배경음악은 사용자의 2026-10-06 결정으로 첫 버전에서 제외한다. 후속 요청 전에는 제작·선정·구매를 진행하지 않는다. 기존 무음 연결은 정상 범위이며 반복·믹싱 청취 인수는 이번 필수 검사가 아니다. -- 한국어 음성은 로컬 Qwen3-TTS로 미리 파일을 제작한다. 기존 conda 환경 `qwen3-tts`를 재사용하고, GPU에서 `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice`의 한국어 `Sohee` 목소리를 사용한다. 실행 담당자가 숫자·안내·실제 도움 문구의 대본과 생성 절차를 준비해 이 환경에서 파일을 생성·검수한다. 게임과 빌드는 Qwen3-TTS·모델·conda 환경에 의존하지 않으며 완성된 음성 파일만 사용한다. CPU·다른 모델/목소리·클라우드 TTS로 제작 방향을 자동 변경하지 않는다. -- 제작 환경의 패키지 설치, 모델 가중치 확보, 모델 로딩, GPU 추론, 파일 생성, 발음·품질 검수는 별도 확인 단계다. 현재 패키지 설치와 클래스 import는 확인했지만 PyTorch는 CPU 구성이다. GPU 실행과 모델·음성 생성은 검증하지 않았다. 개발 실행에서 필요한 GPU 환경을 정비하고 실제 한국어 샘플로 확인하며, 설치나 import만으로 제작 준비 완료를 선언하지 않는다. -- 음성은 고유 ID와 한국어 전체 대본, 고정 모델·목소리·언어 및 생성 설정을 연결하여 제작한다. 문구나 설정이 바뀌면 해당 음성을 새 후보로 생성하고 채택한 원본·Unity용 파일·생성 이력의 대응을 유지한다. 생성 성공과 청각 검수 통과를 구분하며 같은 seed를 기록했다고 동일 파일이 재현된다고 주장하지 않는다. -- 숫자 읽기는 하나~열의 발음과 계수 순서를 보존한다. 빠르게 눌러도 입력과 계수 표시는 즉시 처리하고 발화는 겹치지 않게 순서대로 재생한다. 중복 탭은 발화를 추가하지 않는다. 성공 후 같은 보드에서 자유 놀이로 바뀌어도 대기 중인 숫자 읽기를 끝내며, 축하 소리로 마지막 숫자를 가리지 않는다. 소리 때문에 조작이나 성공 판정을 지연하지 않는다. -- 화면·스테이지 전환과 앱 일시 정지에는 이전 음성 큐를 정리한다. 복원으로 지난 계수·성공 음성을 반복하지 않는다. 상태가 바뀌면 오래된 대기 안내를 취소하며 안내가 계수 순서를 끊지 않는다. -- 제작 원본과 Unity에 넣는 파일을 구별하고, 실제 최종 파일별 출처·사용 조건·생성 도구/모델·대본/프롬프트·수정·검수 기록을 남긴다. 필요한 사용 고지를 결과물에 포함한다. TTS 모델·개발 도구·제작 캐시는 앱에 포함하지 않는다. -- 임시·사용 조건 미확인 자산으로 최종 자산 완료를 선언하지 않는다. 필수 로컬 TTS 출력이 없거나 청각 검수가 끝나지 않았으면 해당 완료 항목은 미완료다. 효과음·배경음악 제외는 미완료가 아니다. 파일 존재만으로 발음·가독성을 통과 처리하지 않는다. diff --git a/.dryforge/002/approval.json b/.dryforge/002/approval.json deleted file mode 100644 index 60a37e5..0000000 --- a/.dryforge/002/approval.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "status": "approved", - "recordedUtc": "2026-10-09T14:14:19.8102344Z", - "userStatement": "완성본을 최종 승인합니다", - "scope": "완성 제품과 변경된 제작·채택·운영·검증 문서", - "branch": "dryforge/hero-meadow", - "approvedHead": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "testedProductSource": "3346eb559bdfba24332feb4d9be4b7104520233e", - "productFilesUnchanged": 732, - "apk": "artifacts/android/MagnetismHeroes.apk", - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "gallery": "artifacts/verification/VisualUpgrade/FinalProduct/index.html", - "finalReview": "artifacts/verification/VisualUpgrade/FinalProduct/final-review.json", - "finalReviewSha256": "F11A355FF4316FADBA7D5532C481A5215F7CBF722F6C207D86D0781906A846EA", - "archive": ".dryforge/002", - "integration": "merged-local-dev", - "unityEditor": "정리 중 에디터 종료나 설정 변경 명령을 실행하지 않음", - "editorObservation": { - "observedUtc": "2026-10-09T14:15:23.2646118Z", - "pipelineInstances": 0, - "runningEditorDetected": false - }, - "integrationEvidence": "artifacts/verification/VisualUpgrade/FinalProduct/dev-integration.json" -} diff --git a/.dryforge/002/archive-manifest.json b/.dryforge/002/archive-manifest.json deleted file mode 100644 index 1083180..0000000 --- a/.dryforge/002/archive-manifest.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "documents": [ - { - "source": "C:\\git\\MagnetismHeroes\\.dryforge\\handoff.md", - "archive": "C:\\git\\MagnetismHeroes\\.dryforge\\002\\handoff.md", - "sha256": "F29029B4AD620E8830408C5A4497A98193259DA709580F98A25343C360648982" - }, - { - "source": "C:\\git\\MagnetismHeroes\\.dryforge\\spec.md", - "archive": "C:\\git\\MagnetismHeroes\\.dryforge\\002\\spec.md", - "sha256": "7F2B2E656B31AEF0124916A5EEA49E6DBF44A93C09C2009B1B59B7CD250FBE1F" - }, - { - "source": "C:\\git\\MagnetismHeroes\\.dryforge\\plan.md", - "archive": "C:\\git\\MagnetismHeroes\\.dryforge\\002\\plan.md", - "sha256": "3074AB3A71C136530933ACA4ABAA52BBD001C52B97D5EB15D53AFC8125FE64EE" - } - ], - "executionSupport": [ - { - "original": "reference/cartoon-landscape.png", - "archive": ".dryforge/002/execution-support/reference/cartoon-landscape.png", - "sha256": "6D26FA1B3232FB0A78963A4D7BA140F610B69B073C99FDD0707E08DAB06DF442" - }, - { - "original": "reviews/final-review.json", - "archive": ".dryforge/002/execution-support/reviews/final-review.json", - "sha256": "F11A355FF4316FADBA7D5532C481A5215F7CBF722F6C207D86D0781906A846EA" - }, - { - "original": "reviews/horizon-fix-conformance.json", - "archive": ".dryforge/002/execution-support/reviews/horizon-fix-conformance.json", - "sha256": "5EBA704C16AB4E7319C10AAD8E337B8737E974DF99966FF4B9D05040F3AC3E22" - }, - { - "original": "reviews/model-conformance-T1.json", - "archive": ".dryforge/002/execution-support/reviews/model-conformance-T1.json", - "sha256": "C134B8ACB71ED8A95F6B4A094B9D85670EED69AAA5FA97098EEC5C1B680B9AA0" - }, - { - "original": "reviews/model-conformance-T2.json", - "archive": ".dryforge/002/execution-support/reviews/model-conformance-T2.json", - "sha256": "C65F5BB38C25EBFAFB09FD1032A8ECBD5994B19CD4D01C68BC9A08CA5BCD3BE2" - }, - { - "original": "reviews/unity-conformance-T4.json", - "archive": ".dryforge/002/execution-support/reviews/unity-conformance-T4.json", - "sha256": "2C67D6BC681F614BB73B0BFA80A4968FD7CA259B9EDEFDDEED219E250AC8385F" - }, - { - "original": "reviews/unity-conformance-T5.json", - "archive": ".dryforge/002/execution-support/reviews/unity-conformance-T5.json", - "sha256": "7CE20CD8554FD7BF99F8FDD939E2162E9F32815AE1381600431DAAE8174EB336" - }, - { - "original": "audit-final-product.py", - "archive": ".dryforge/002/execution-support/audit-final-product.py", - "sha256": "870C2FBE370BA8C7AF67A3359FCF1D7F6DCA700673B1703AB8087BDF1C6C94AA" - }, - { - "original": "execution.json", - "archive": ".dryforge/002/execution-support/execution.json", - "sha256": "E2A5CC7A7E19023C6A6C3ED01BA35DBD09795E5485B7402E63537D15E0A59224", - "previousSha256": "0317C3BB8866D341B355BF278EA1DB8D30A3E37B001A1D7E8532E1CB25B0886F", - "updateReason": "사용자 승인에 따른 로컬 dev 병합 완료 기록" - }, - { - "original": "final-product-checks.ps1", - "archive": ".dryforge/002/execution-support/final-product-checks.ps1", - "sha256": "E46FD552A3E7C1B75FA7B0357D5B264FDABD2597E0A27B350DCC64EB566771AA" - }, - { - "original": "preserve-before-horizon-fix.py", - "archive": ".dryforge/002/execution-support/preserve-before-horizon-fix.py", - "sha256": "920B812B0E4724FE38EEE8BE1D4F114825D4511DA66AB9E3728AA12A3952B9D4" - }, - { - "original": "preserve-failed-smoke-and-prune-stale.py", - "archive": ".dryforge/002/execution-support/preserve-failed-smoke-and-prune-stale.py", - "sha256": "35E995C03C067A73ADD4748AC7D974C797DBC17627013CBC39A239E65A91AA77" - }, - { - "original": "preserve-horizon-failed-smoke.py", - "archive": ".dryforge/002/execution-support/preserve-horizon-failed-smoke.py", - "sha256": "A7C077D9CE75F3CDA5C857CA14607DD15F080A3FF25F8326ED6C67600995FDDC" - }, - { - "original": "preserve-previous-final.py", - "archive": ".dryforge/002/execution-support/preserve-previous-final.py", - "sha256": "017023C3852D7A687BE2707B7E5C28C1AA73A0662E44F5479402843DFB531EE3" - }, - { - "original": "record-final-user-gate.py", - "archive": ".dryforge/002/execution-support/record-final-user-gate.py", - "sha256": "EB584202EDF13CCE5CB13A1A2DB67154B32E2022FD19F46B1903B104AF2874F2" - }, - { - "original": "record-horizon-integration.py", - "archive": ".dryforge/002/execution-support/record-horizon-integration.py", - "sha256": "AB851721DC0C4FF2191799C8D338923BA13C595B7CF49DEB70ED3370318A5BE8" - }, - { - "original": "record-model-approval.py", - "archive": ".dryforge/002/execution-support/record-model-approval.py", - "sha256": "2CCF692FE021EF1CBB6858676BEFA02E650DE318E6CEA9C3F5D091D2E15544FE" - }, - { - "original": "record-new-completion.py", - "archive": ".dryforge/002/execution-support/record-new-completion.py", - "sha256": "A3261E88C43C986072637BF3F996014F0C3B92BD98DCB0360A3DCF5237C97AA2" - }, - { - "original": "stage-visual-boundary.py", - "archive": ".dryforge/002/execution-support/stage-visual-boundary.py", - "sha256": "25AD7E9EC7D952C47E3875556A024DF54EA3B18F6090B6C95D17FA7779A4AAF8" - }, - { - "original": "verify-model-integration.py", - "archive": ".dryforge/002/execution-support/verify-model-integration.py", - "sha256": "DCD39C0FA4B58E2B9BA3A82298B27D24D76D9D3191EB6F4D79A4A34D010E5196" - } - ], - "worktrees": [ - { - "path": "C:\\git\\MagnetismHeroes\\.dryforge\\worktrees\\T1", - "commit": "7d7efbe6b6a1a419ac998f37e3e019a75570a9e5", - "clean": true, - "ancestor": true - }, - { - "path": "C:\\git\\MagnetismHeroes\\.dryforge\\worktrees\\T2", - "commit": "80be80566a309eab8cb333ea9774218c816efc30", - "clean": true, - "ancestor": true - }, - { - "path": "C:\\git\\MagnetismHeroes\\.dryforge\\worktrees\\T4", - "commit": "ef03611628cb395d377cf56abd951bbf62cfa29c", - "clean": true, - "ancestor": true - }, - { - "path": "C:\\git\\MagnetismHeroes\\.dryforge\\worktrees\\T6", - "commit": "620c73cbd24d936231bee5cb3469e5cff3424cd9", - "clean": true, - "ancestor": true - } - ], - "taskBranches": [ - { - "branch": "dryforge/hero-model", - "commit": "7d7efbe6b6a1a419ac998f37e3e019a75570a9e5", - "mergedIntoApprovedHead": true - }, - { - "branch": "dryforge/meadow-model", - "commit": "80be80566a309eab8cb333ea9774218c816efc30", - "mergedIntoApprovedHead": true - }, - { - "branch": "dryforge/unity-visual-upgrade", - "commit": "43b22d67886bc5715d3543fefda2dc24dead299f", - "mergedIntoApprovedHead": true - }, - { - "branch": "dryforge/visual-verification", - "commit": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "mergedIntoApprovedHead": true - }, - { - "branch": "dryforge/final-delivery", - "commit": "a906be69a0c1211f01d5a3d1bc7fdcf5554efafb", - "mergedIntoApprovedHead": true - }, - { - "branch": "dryforge/horizon-seam-fix", - "commit": "ef03611628cb395d377cf56abd951bbf62cfa29c", - "mergedIntoApprovedHead": true - }, - { - "branch": "dryforge/horizon-final-delivery", - "commit": "620c73cbd24d936231bee5cb3469e5cff3424cd9", - "mergedIntoApprovedHead": true - } - ] -} diff --git a/.dryforge/002/closure.json b/.dryforge/002/closure.json deleted file mode 100644 index 718b444..0000000 --- a/.dryforge/002/closure.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "status": "completed", - "finishedUtc": "2026-10-09T14:14:24.8264555Z", - "archive": ".dryforge/002", - "documentsMovedAndHashVerified": 3, - "executionSupportFilesPreserved": 20, - "removedMergedWorktrees": 4, - "removedMergedTaskBranches": 7, - "productFileHashesUnchanged": 732, - "headUnchanged": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "protectedDevUnchanged": "58410281e015c6600dc543c4e093fd95c036288c", - "remainingDryforgeItems": [ - "001", - "002", - "status.json" - ], - "integration": "merged-local-dev", - "unityEditorUnmodifiedByCleanup": true, - "editorObservation": { - "observedUtc": "2026-10-09T14:15:23.2646118Z", - "pipelineInstances": 0, - "runningEditorDetected": false - }, - "integrationEvidence": "artifacts/verification/VisualUpgrade/FinalProduct/dev-integration.json" -} diff --git a/.dryforge/002/execution-support/audit-final-product.py b/.dryforge/002/execution-support/audit-final-product.py deleted file mode 100644 index 3b4bf47..0000000 --- a/.dryforge/002/execution-support/audit-final-product.py +++ /dev/null @@ -1,58 +0,0 @@ -import hashlib -import json -import subprocess -import xml.etree.ElementTree as ET -from pathlib import Path - -root = Path('C:/git/MagnetismHeroes') -proof = root / 'artifacts/verification/VisualUpgrade/FinalProduct' -final = root / 'artifacts/verification/Final' -def read(path): - return json.loads(path.read_text(encoding='utf-8-sig')) -def sha(path): - return hashlib.sha256(path.read_bytes()).hexdigest().upper() -completion = read(proof / 'completion.json') -source = '3346eb559bdfba24332feb4d9be4b7104520233e' -assert completion['status'] == 'passed' and completion['sourceCommit'] == source -assert len(completion['phases']) == 4 and completion['productHashesUnchanged'] -for phase in completion['phases']: - assert phase['exitCode'] == 0 and phase['status'] == 'passed' - assert phase['sourceCommit'] == source and phase['sourceDirty'] is False and phase['sourceDirtyEnd'] is False - raw = read(Path(phase['evidence'])) - assert raw['status'] == 'passed' and raw['exitCode'] == 0 and raw['sourceCommit'].strip() == source -counts = {} -for mode, count in [('editmode', 103), ('playmode', 53)]: - cases = ET.parse(final / (mode + '.xml')).findall('.//test-case') - assert len(cases) == count and all(case.get('result') == 'Passed' for case in cases) - counts[mode] = count -apk_hash = sha(root / 'artifacts/android/Final/MagnetismHeroes.apk') -assert sha(root / 'artifacts/android/MagnetismHeroes.apk') == apk_hash -for path, count in [ - ('android-final-evidence.json', 226), - ('Fresh30/android-final-progression-evidence.json', 151), - ('FinalBoundary/android-final-boundary-evidence.json', 37), - ('RapidBoundary/android-final-rapid-evidence.json', 39), - ('ContentBoundary/android-content-evidence.json', 69), -]: - data = read(final / path) - assert data['status'] == 'passed' and data['sourceCommit'].strip() == source - assert data['apkSha256'].upper() == apk_hash and data['sourceDirty'] is False - assert len(data['checks']) == count and all(check['passed'] for check in data['checks']) - counts[path] = count -before = read(proof / 'product-before.json') -after = read(proof / 'product-after.json') -assert before == after and len(before) == 732 -assert all(sha(root / path) == digest.upper() for path, digest in before.items()) -assert subprocess.check_output(['git', 'status', '--porcelain'], cwd=root).strip() == b'' -assert subprocess.check_output(['git', 'rev-parse', 'dev'], cwd=root).decode().strip() == '58410281e015c6600dc543c4e093fd95c036288c' -for failure in completion.get('failedAttempts', []): - assert failure['exitCode'] == 1 and failure['status'] == 'failed' - archived = read(Path(failure['evidence'])) - assert archived['exitCode'] == 1 and archived['status'] == 'failed' - assert Path(failure['outerLog']).is_file() -report = {'status': 'PASS', 'testedProductSourceCommit': source, - 'currentHead': subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=root).decode().strip(), - 'counts': counts, 'apkSha256': apk_hash, 'productFileHashesUnchanged': len(before), - 'failedAttemptsPreserved': len(completion.get('failedAttempts', [])), 'protectedDevUnchanged': True} -(proof / 'root-final-product-audit.json').write_text(json.dumps(report, indent=2) + '\n', encoding='utf-8') -print(json.dumps(report)) diff --git a/.dryforge/002/execution-support/execution.json b/.dryforge/002/execution-support/execution.json deleted file mode 100644 index a14aa28..0000000 --- a/.dryforge/002/execution-support/execution.json +++ /dev/null @@ -1,917 +0,0 @@ -{ - "feature_branch": "dryforge/hero-meadow", - "protected_base": "dev", - "initial_commit": "58410281e015c6600dc543c4e093fd95c036288c", - "started_utc": "2026-10-08T01:35:25.790795+09:00", - "protected_product_hashes": { - "Assets/InputSystem_Actions.inputactions": "1c6cbb519d65ecfe19a29231735b7f54741a20ac1a8ad4f46a18e1bfb28a3c39", - "Assets/InputSystem_Actions.inputactions.meta": "3b4e424a80f5b4fa76b6abd774884a24285eaa61f22ff9bb20e5ad505fd8b569", - "Assets/MagnetismHeroes.meta": "a4234b3a59055b04475ba74daad6190a023683099de4cb4fd08644651843dd9c", - "Assets/MagnetismHeroes/Core.meta": "de2805e10269b6d5230d5737d514ba7bd882aeed21c9709061a04d418758a09e", - "Assets/MagnetismHeroes/Core/AGENTS.md": "a80bb0bf851d469e6ed975e95a5849d6888d8f01a0991725b96a5cdcdc1bc715", - "Assets/MagnetismHeroes/Core/AGENTS.md.meta": "c40cb22815e8ed2264dabd920de5ba7a261cfa19f7bf1def141dbaf8eebbad36", - "Assets/MagnetismHeroes/Core/AssemblyInfo.cs": "7b8cc02b8a3798cddf7f944256f8ec7caf4ad5110df2203fa94c12b0159a9ae2", - "Assets/MagnetismHeroes/Core/AssemblyInfo.cs.meta": "1192dd1392f04fe3b208df16c762396e8d918e0f0d2bd26cba932ee8bde01aa3", - "Assets/MagnetismHeroes/Core/BoardHint.cs": "cc3dc3f021d8a8de5d99e3c4d2f8c4fdec5de75ebbafaf5e0777e64629c4de79", - "Assets/MagnetismHeroes/Core/BoardHint.cs.meta": "cfc017f9dc78f2993b29af89fe8b2ca81bf225613e6b9456abcc6757306f4762", - "Assets/MagnetismHeroes/Core/BoardRules.cs": "e1bba538b0dc9176ce451c5a940a6cde6c5004590a8e669949425d1162772920", - "Assets/MagnetismHeroes/Core/BoardRules.cs.meta": "1c790281e4d8ed75501199425d97d417a31c512eaaf68df1cc5d50c5483ab065", - "Assets/MagnetismHeroes/Core/GameData.cs": "854912cffe249a4cf23013a103a1750b15016ca3fcd9c6360f388d806e6a1942", - "Assets/MagnetismHeroes/Core/GameData.cs.meta": "4049c18bdf2bc1650039a7e761253b188488304840f5cbd1fb27109fd28d16ba", - "Assets/MagnetismHeroes/Core/GameSession.cs": "d5ac5b37f5e133614cb574a191929432f1802a9887ff08154b2280eb9f644c97", - "Assets/MagnetismHeroes/Core/GameSession.cs.meta": "967e33fafdd69d6df8afd63a79a76ee5a6b81c35c55a3481dee8a5aeccdf09fd", - "Assets/MagnetismHeroes/Core/MagnetismHeroes.Core.asmdef": "a3b77330b9bd624393eb31b5123c1b311f5ad168a9286afe24cb07b5051c5f3b", - "Assets/MagnetismHeroes/Core/MagnetismHeroes.Core.asmdef.meta": "59f47f86977a939fc944ae8235ef8c3ab9567edf17d0205477c614a2807fb5ac", - "Assets/MagnetismHeroes/Core/SaveValidator.cs": "339c27b556d2ec1f279da9cb211bcc46fc5e1cb99df4103618fa1633ecc04baa", - "Assets/MagnetismHeroes/Core/SaveValidator.cs.meta": "7bb6a48c26b24e8c4ee8b12a69906b22367b232f0fabf5f11663085c68be8bfb", - "Assets/MagnetismHeroes/Editor.meta": "392ee7b3635d4a43568a780cee7b982d3e21c9bc45533d9452c31448611fe1ff", - "Assets/MagnetismHeroes/Editor/AGENTS.md": "c448aa32d82c999d294634eb10fda77b2a570e0cdbe2ba9314991517f900fd60", - "Assets/MagnetismHeroes/Editor/AGENTS.md.meta": "6a1ced2b558b739144f509d5574f93c6b1db650dd1ec34f55803ce0db0ac1df8", - "Assets/MagnetismHeroes/Editor/AndroidBuildSanitizer.cs": "9946d40e8d6d8450a51155f9183d35bc789084f44f4a685b51774914cb055d49", - "Assets/MagnetismHeroes/Editor/AndroidBuildSanitizer.cs.meta": "30f7c19b85d5d3d554c82ec97c1495a7f59a19cf4acf9dfc45de901302eec2bc", - "Assets/MagnetismHeroes/Editor/CompositionAssets.cs": "5c03d7a80205b9c83b680ab4bfd41e016f3c53bd534ecd2a10cd0c4c91eb9562", - "Assets/MagnetismHeroes/Editor/CompositionAssets.cs.meta": "3e4f8eba6f5af6977d896574eb348aac946d6ee81405add081411f960a8df20f", - "Assets/MagnetismHeroes/Editor/ContentAssets.cs": "f79ec21a4868c7bbff8ebfc7777e7ed7fed574797658f730e2d70643ac8b2d43", - "Assets/MagnetismHeroes/Editor/ContentAssets.cs.meta": "21c02d17dc78f57386f8de2f244d1a53a145977fa3b658049838cdc7ad423332", - "Assets/MagnetismHeroes/Editor/FirstSliceAssets.cs": "1641225508a07451bd283682bc25c4f24ac21fdab18a873ceb5a02994eb2f819", - "Assets/MagnetismHeroes/Editor/FirstSliceAssets.cs.meta": "b989e9614c406e1313593508980e50e3ddd846d5a43cb01d51e29e2d6569f9ed", - "Assets/MagnetismHeroes/Editor/MagnetismHeroes.Editor.asmdef": "f2b62ac5f1186367e40021d12c878b6f057fc3ce7b4672adfcb2211189a32762", - "Assets/MagnetismHeroes/Editor/MagnetismHeroes.Editor.asmdef.meta": "c139a049cb5cbad7782d39566fcc2cf6f13a5822524640c5d4df3ef8b1c033e1", - "Assets/MagnetismHeroes/Editor/PackageInstaller.cs": "738a95b580c4bf70ae095dc52962b711661192fbdfb9c5206d9c51d8b3309d21", - "Assets/MagnetismHeroes/Editor/PackageInstaller.cs.meta": "a30063297a6235e8be9eebbf28f13e97ee3a7f68e40c9ba84483a93699f7a197", - "Assets/MagnetismHeroes/Editor/PersistenceAssets.cs": "a9e3d3e7d70b6d458904f92a82af2faac8fdc06d597a77c26b724d7367c128d5", - "Assets/MagnetismHeroes/Editor/PersistenceAssets.cs.meta": "00e7a48d1e203ae7e0c5c8337d797992ab5efb34ac17a470d638e9b934eaeb8c", - "Assets/MagnetismHeroes/Editor/PresentationAssets.cs": "a530b8fe72f26b242fd8482a83082b1bd99743b5538b713bfdeeb56b87f84a13", - "Assets/MagnetismHeroes/Editor/PresentationAssets.cs.meta": "46e6e43a82ab1141f43b30781673cd56a2f25b84efdbae27847e39e0bde85ed8", - "Assets/MagnetismHeroes/Editor/PresentationProof.cs": "1c350672efcd719302a92b91f8528cd0331ae483d37f4d7053491d603f798f0b", - "Assets/MagnetismHeroes/Editor/PresentationProof.cs.meta": "da0a39a723e2436d923b2312120a0e309b9f64c5948f69f1c9fbe5cb73cce089", - "Assets/MagnetismHeroes/Editor/ProjectBootstrap.cs": "0b8b95d8cb5c93bff3721fa8b34e97b2a8658df8ce75c9ceb75b30c1ad29384a", - "Assets/MagnetismHeroes/Editor/ProjectBootstrap.cs.meta": "079d1d993195e12ddd9c273345915818561459869397ba016416161efd69c81e", - "Assets/MagnetismHeroes/Scenes.meta": "b08de2ba7963250cc8201e9602085974164758f6cd7486d6702b191cdcfdb27c", - "Assets/MagnetismHeroes/Scenes/Main.unity": "26154649ce0c4cdd160d053227c1fd6f0190e9ae829f2be0ccb427f5deeb357a", - "Assets/MagnetismHeroes/Scenes/Main.unity.meta": "fb9e84bc5440c5d697e941526e912df3c08bb066b4078b9d027f0ad3b8412120", - "Assets/MagnetismHeroes/Tests.meta": "18ce57c5f66e3efa85146b4d53a0454630d77d2c9ab1a1ca67501541132eb7d4", - "Assets/MagnetismHeroes/Tests/EditMode.meta": "a101782b25f079b6daca580215b74d05651604f56daa24bbb62d11b635136869", - "Assets/MagnetismHeroes/Tests/EditMode/CompositionAssetTests.cs": "671d8d1a790d531eb7684b71de92d3503a6d7eba91d24701613cc55dfdb79aac", - "Assets/MagnetismHeroes/Tests/EditMode/CompositionAssetTests.cs.meta": "18afc2728da017b757b6bc8092e4eb1d5866bc0b1d614e702bfdd72c4b563738", - "Assets/MagnetismHeroes/Tests/EditMode/CompositionSessionTests.cs": "52eaa3b3edc993e0e916e96eaf2144b72f4dbed0738ff1f3cb8e16e3eecf0454", - "Assets/MagnetismHeroes/Tests/EditMode/CompositionSessionTests.cs.meta": "73c88128561e5328bafb35e579e86c265f5df73ea33d52ce4f0398a7631c0a11", - "Assets/MagnetismHeroes/Tests/EditMode/ContentAssetTests.cs": "d7ead26afbcaba631f58bcfbb0e84b9c58c79f61915c05df3caaf829afe13ec9", - "Assets/MagnetismHeroes/Tests/EditMode/ContentAssetTests.cs.meta": "c2e30ce735635744a27aedacf054129cdc017b736839314bc8f03b871d7f5c99", - "Assets/MagnetismHeroes/Tests/EditMode/ContentSessionTests.cs": "53b28af4e7ef50a80f35815948ff7e58e4eaed3581bf9b1ba4fda9b687f17afc", - "Assets/MagnetismHeroes/Tests/EditMode/ContentSessionTests.cs.meta": "fd12187acbe9ae1a53c0b4a8ce27755abd1f171d4a223a8e5fc16497f3bb8b9e", - "Assets/MagnetismHeroes/Tests/EditMode/ContentVoiceTests.cs": "0a98907804ae976dbac37bf1d2b2211905f17059871b490b870f79712f45591b", - "Assets/MagnetismHeroes/Tests/EditMode/ContentVoiceTests.cs.meta": "95cf38a75101caaedf3c7f7b59328460bbccbbc7bd82c96be5e34a816518c240", - "Assets/MagnetismHeroes/Tests/EditMode/Core.meta": "be46851d141c126a9706049aee91e375c76dd3747ca109271d881f2015e748ab", - "Assets/MagnetismHeroes/Tests/EditMode/Core/BoardRulesTests.cs": "0466ffa2f94a3ec59cddea4267ca7591f1a2f585343902e85666302fee548a17", - "Assets/MagnetismHeroes/Tests/EditMode/Core/BoardRulesTests.cs.meta": "04dcdd8030b52c7350d1249dcfb457259655049a6ef46e030f22960d80eac21f", - "Assets/MagnetismHeroes/Tests/EditMode/Core/SaveValidatorTests.cs": "141f1f53aa66aa9e7372828c2e8ee6f4237c6ec20356ca201164dc014f88e1e9", - "Assets/MagnetismHeroes/Tests/EditMode/Core/SaveValidatorTests.cs.meta": "1254a358e3bd9efeb86212abded98d50ee54a594cf61b1e07f01d1ff1f1ff505", - "Assets/MagnetismHeroes/Tests/EditMode/Core/StageLayoutTests.cs": "310ebf2b56eb4d6fdf1e23ef24b7714ef3a069f1ea6610f07a5d7e02ab7b1708", - "Assets/MagnetismHeroes/Tests/EditMode/Core/StageLayoutTests.cs.meta": "4c8ba2f93312e3661a919c843c889c89bd52d16133efa1c5924653bde25c921c", - "Assets/MagnetismHeroes/Tests/EditMode/FirstSliceAssetTests.cs": "7ebe9bb7eec107502c765c952ba066e89f6677674888579b840f2ec915a144c3", - "Assets/MagnetismHeroes/Tests/EditMode/FirstSliceAssetTests.cs.meta": "189dba50e2a632b9c9a823f13a8ab402b8f71860ec75b67fb337c7d3f3f2eb17", - "Assets/MagnetismHeroes/Tests/EditMode/GameSessionTests.cs": "eebeb7e6aaafc6f1cbb7cd1a263e5612b2a86db83a4b4fc03fbf71f9697ec4b0", - "Assets/MagnetismHeroes/Tests/EditMode/GameSessionTests.cs.meta": "4bfeea0e3c0ca73ed34013fec015adbb3826e402484dfd536c44c41c76c475be", - "Assets/MagnetismHeroes/Tests/EditMode/LocalProgressStoreTests.cs": "c21e95031974cc68ec624627176c2d30b75aba5ff724794b07737c9f1aeb7051", - "Assets/MagnetismHeroes/Tests/EditMode/LocalProgressStoreTests.cs.meta": "185b21bbc833a65793a8cd42f50fdd16c42f171f6150f82004c190d0fd053dd5", - "Assets/MagnetismHeroes/Tests/EditMode/MagnetismHeroes.EditModeTests.asmdef": "6ea5923da2fe43e9a968be26502c504e7730913b357944a40c061e131b78ee56", - "Assets/MagnetismHeroes/Tests/EditMode/MagnetismHeroes.EditModeTests.asmdef.meta": "0f437b8a74cc8240882f01b2b649ef8087e8c3ef66b4a4a9a8f412d0bf9dae50", - "Assets/MagnetismHeroes/Tests/EditMode/PersistenceSessionTests.cs": "f5445798e640239142161cd83b7c9b321d309b4f6ed8ab1433b740f8d7053ff5", - "Assets/MagnetismHeroes/Tests/EditMode/PersistenceSessionTests.cs.meta": "5913c492b66095714d8e0150192d0f7672bd923b79339ce96b56d8c260152549", - "Assets/MagnetismHeroes/Tests/EditMode/PresentationAssetTests.cs": "58efeba812f22c28b2dd721ca3fe94afcea220f4b87e2a9f58672ed393903ff4", - "Assets/MagnetismHeroes/Tests/EditMode/PresentationAssetTests.cs.meta": "0bd7661784fb24aae755f6b2722360b2fc1a15cec75cc2760f3f5f29f66090fe", - "Assets/MagnetismHeroes/Tests/PlayMode.meta": "d4fdb69ba2114665648c9465fb5377289690098ff5ad75d13c79c4af6a7bd669", - "Assets/MagnetismHeroes/Tests/PlayMode/CompositionPlayTests.cs": "8fb76df4bcbb777ebfa163b7d99ce755625cf3dbc12775a161732ff13a0a660d", - "Assets/MagnetismHeroes/Tests/PlayMode/CompositionPlayTests.cs.meta": "f24b1269ff391a00693dcdd856e1ea64901f74ec4b14cf44ff300f6d3986b91a", - "Assets/MagnetismHeroes/Tests/PlayMode/ContentPlayTests.cs": "55be21e779132f693d05cf28780d5c7d4b2d190cf0b5bdf5395923efaf0f849f", - "Assets/MagnetismHeroes/Tests/PlayMode/ContentPlayTests.cs.meta": "2f260827c964055294d2d2f155a67f4a0951ebe4a6fe470b1f13bb8e35bbe550", - "Assets/MagnetismHeroes/Tests/PlayMode/FirstPuzzlePlayTests.cs": "38d28d56d02e46ff190d347936119232cbde3e0c748c875cdb075cb44dcb0d1a", - "Assets/MagnetismHeroes/Tests/PlayMode/FirstPuzzlePlayTests.cs.meta": "722c0e3a05254eafb1206303cc0b412aff57d8f1ec26a76b30a0eeb46069d4af", - "Assets/MagnetismHeroes/Tests/PlayMode/MagnetismHeroes.PlayModeTests.asmdef": "06c4bfc9c973f9d1610560687fed27d3505ea0807a626744d5da5544733d8790", - "Assets/MagnetismHeroes/Tests/PlayMode/MagnetismHeroes.PlayModeTests.asmdef.meta": "9518a4247dfe92ac5b4a3fa1e262dcd4997a20ea636fde37d9a77af71587aa61", - "Assets/MagnetismHeroes/UnityRuntime.meta": "33a905e7ef9b11f270fbc5f086714d34edc7ba2bbfff4353f69fe7e295ea1a58", - "Assets/MagnetismHeroes/UnityRuntime/AGENTS.md": "0f6ffc45d7b1c894a791320e8bce1b80e9bac9aa225202176e4edab123b5db90", - "Assets/MagnetismHeroes/UnityRuntime/AGENTS.md.meta": "1670b8878b8c1c14f2ef35ad51a3888821bb382f67d3bd401bd3a8d5c52c8c49", - "Assets/MagnetismHeroes/UnityRuntime/AssemblyInfo.cs": "4b227cad2c5dcd6e90da3c32640259466a124890e51a2ae343023c758c7840a2", - "Assets/MagnetismHeroes/UnityRuntime/AssemblyInfo.cs.meta": "410c26c65fc3833181d8c0809416f38e4eebb691efac8c61da702eecc40d805b", - "Assets/MagnetismHeroes/UnityRuntime/Bootstrap.meta": "f50ddba1d8572fdf2c9f1d4ee3114da1d69bb8e70a4a80132c56b7d85e7bc7b9", - "Assets/MagnetismHeroes/UnityRuntime/Bootstrap/GameApp.cs": "54fff47f4c6bc9a7c3809027ce15f57e6f1f4a90f8095852149451619a4d07a5", - "Assets/MagnetismHeroes/UnityRuntime/Bootstrap/GameApp.cs.meta": "bd7997f83ee94e0669fcc3d18ef25e8bc290c5e1b03a1f9c66bd76fddb7253d2", - "Assets/MagnetismHeroes/UnityRuntime/Interaction.meta": "8c347d091439696bc3d3b2852c87696c9b410554cbc9a65abbd1648c3f49731f", - "Assets/MagnetismHeroes/UnityRuntime/Interaction/BoardInput.cs": "41823185afead383900aabe85a99b2fda73a4a70ccd768e55418876b4f98b0f9", - "Assets/MagnetismHeroes/UnityRuntime/Interaction/BoardInput.cs.meta": "1211f814ccc275b79080807db6b6014d703a08e49a8504035fd7e7cc4417c3a5", - "Assets/MagnetismHeroes/UnityRuntime/Interaction/OrbitCamera.cs": "78d71cab34243d4fe68a51ebddc41a2adab1b40b1e58c10bd94a2f16bd62f223", - "Assets/MagnetismHeroes/UnityRuntime/Interaction/OrbitCamera.cs.meta": "c0962976f8ab5219d74f02d0a8058b77358f18a44a6bca0ccbf9721ef727fcd0", - "Assets/MagnetismHeroes/UnityRuntime/MagnetismHeroes.UnityRuntime.asmdef": "8e618272389c248dc28d7e0261ea52d45387ac90bc46a7821576c9c1a17bca52", - "Assets/MagnetismHeroes/UnityRuntime/MagnetismHeroes.UnityRuntime.asmdef.meta": "78d46f2e1f511baada1b617c328ad0374e1880545ca27764f7dba8a52157ec24", - "Assets/MagnetismHeroes/UnityRuntime/Persistence.meta": "e08035f8440cffe9537c612b97c72b580bb27474d7de66cd81b013ae5784fd25", - "Assets/MagnetismHeroes/UnityRuntime/Persistence/LocalProgressStore.cs": "535278ccc141990f00342d6d32b12bff039e6e742fe494b82adbd39f74de46d0", - "Assets/MagnetismHeroes/UnityRuntime/Persistence/LocalProgressStore.cs.meta": "7ac5e2b29573063595f1e15bed404acdc04b1b0c45f90d75808b9746a1b7ff79", - "Assets/MagnetismHeroes/UnityRuntime/Presentation.meta": "093df3389c613b936dd393cf3625ff0f26bfd408d0f7047b37eeb94762b702f9", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio.meta": "d0a391ffadaef257630a38e23f5aaa923f616b600ea2655ff24c59ab88230344", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/GameAudio.mixer": "54ab67ac0e2ac14ad3cfdcb10ce1ff4ab08f921a0f31fd5bc9d4a651f5594247", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/GameAudio.mixer.meta": "bddf1fe71193cbade06c9e1e013f4ac127b95e1060df840ceff2f556984cadd1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice.meta": "085d19326a2646680cac08c6a5f4cf7c94b84971955696375fe3ebc7dacc5361", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_01.wav": "13a882ce1620140d447635e14f2d4758045d5e348960c17386df1c720050c100", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_01.wav.meta": "485dd82a353c2fb267ddb8b13d92ee77344049ac04040df98fb4ffae3d3d3a1a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_02.wav": "278dd0b6e96f2abca2237ff98eda453a011fa0b2e78d5575b7ddc9c9b3028b32", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_02.wav.meta": "39e0960145943bda38822ed7942ab58d4c22c24414bc571a214eb6b9c4e89c57", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_03.wav": "4133b0150aa08e26f1b93b11d29ecab1282bbff236b3c92f8416e781109188c1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_03.wav.meta": "fba28c38f33a759b2224f5c4c52a9ac4c7aaed42339733b7752fd8d5fde03d40", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_04.wav": "c5c8c78ccd89011050d7317960a854130e3e445c8b92d23e9520eeb7490aa300", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_04.wav.meta": "8f12f33d293025d7738539b5de2350221e8c63aa3ea3bf7869af786406a2f216", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_05.wav": "700b9827ea827adeae0ebfc3b4af2b9125d7d3eddc699c63c0ce5c1ba5cbd04c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_05.wav.meta": "718c3625f6cfa26459a58cfd39200b5bfbbbe26c5b6c28071320fba2e6a00db8", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_06.wav": "d4800a23a73d1315987d9129d260baa0680621d6502351137845a6ee351c6274", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_06.wav.meta": "deef2078506c24ccd884be8427bf9cb7e72391e430c7354dcfac5003fd2319b2", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_07.wav": "7d4ba785ccb28b8f2bf0d8f66053dc8adf1a19d332bedfab3bffd596208fe829", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_07.wav.meta": "394160eee1e91790b2608ec186693cc4b319fe39608cf5cb6eb7930c8112b625", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_08.wav": "fa8034948f6ac3e289e8e7e9fb447943760d4ed5ac577c8ab0c961c608fe49c2", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_08.wav.meta": "bda6f2d81d14dd0607c738ff5b36fc451bd0bd5f6c273f9d6db39d572c357d8a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_09.wav": "4a921bc405ed5181e26365764ef94c2d311af45ed5508b27ffd7ac6d53d83802", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_09.wav.meta": "13cc0421fcfd58e85c6c0d32e7ce27f422d35d0c6ed778f672ab3c44fdcf36f4", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_10.wav": "b16ebccea20aedf93b884299edb0dd430f67e4deef069fb354b482f21b8e5fb8", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/count_10.wav.meta": "9e4047deae1118cc646c3f4bfe60766522cd63f187bb7b93a68707da384c709e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/demo_composition.wav": "570452e9fa15ec545b126cd8bdba504a3db6f30b74a95405ee228015368761e7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/demo_composition.wav.meta": "253482825f08dad867f1d0edab230bde3e27fcc7a3216535ea92c7eb4c4a5aec", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/demo_merge6.wav": "6300c5490442c3168a52611888568ce37fdaffa82b1b72780e5c9eb5ce81fa53", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/demo_merge6.wav.meta": "de3a9691371d5f3e2d27f22228651e2e6d1e8e0c8a50d9213fc56305fa8ff5f5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/demo_split10.wav": "6bc2d42b24170fa0dda48ed8c5a24b147219e7237d9989432026cbf028e81712", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/demo_split10.wav.meta": "ae826ee7337d2dc3eb69159a6a693053a4daefb03d94434506927c94ea543c9b", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_collect.wav": "62e15c9e38c36f4094a6c7e9c5148510141290b5f41db5aad9dc1d8f1a158594", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_collect.wav.meta": "efed428f46d5019e3e55c6f5a8942f35b2bbd6762d0a34719bb52394f1a8e65f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_count.wav": "debe29b89f3e2856f2bf4c5e01707a5f0a0657e5768c03be385ec88fa2328b40", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_count.wav.meta": "b5cc454fb99ec1d96b026d472cff2e11dcf461a50b4ee72ee5d9547424991774", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_find.wav": "14de802d534e95053b89c93f8ddd86510e1f68ac4bbf790f8b1f5274c2c34c41", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_find.wav.meta": "2f4ada332bdd1f4e0a1093c8a9b9bd6d9888de52851a251b3fbc9589ec405b9c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_merge.wav": "522db037e0358b01b84dfc68f13e62073495a011a96d8ca407be28f36e98e16d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_merge.wav.meta": "737f730ba365c64a03f00c72075ac3ff1f9c1508c38739c07bf3f4a8e6f43ec5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split.wav": "72df6edc53a6e320e48b6bae18ba6bed43034028795021ff48d6f04bc4dc7649", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split.wav.meta": "6734167fefec385cb723dc63ce4af9a17231b163aeb468cb9191485771a00a12", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_1.wav": "045b2e2065ba0eb44fc5c49405227b7dc042a0a20e88b5aeae87bbea24d0a0c7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_1.wav.meta": "e3cd6effc9df1ba883dcf97d15c94a4d10fdb5a676883adc9e3cff6fec08ab1e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_2.wav": "f3d7912563c761ca8ebace355000ceb1522f6f31a2e15735c712c0d26d011ed6", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_2.wav.meta": "fdf14437e475f79953de36042796674e8d1dc7fef7f4a641c3e196b8904e9d93", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_3.wav": "dc7f9f9750379fae5b7e8067d467cf6704a6501c3f1a57f4b50a93191ac7140f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_3.wav.meta": "2847f192b3ca561be8157d325de552df3ac40627eb52efb13f0a9672617dfb33", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_4.wav": "d1ad45d2432befcfc4d78c29c5c355bdd42eb02492c6ed668c3c0d8ce9c0822d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_4.wav.meta": "2e433be17e683beb83da1edb21b729a23b37ee0ae3f450dd1d8a5ff18f5002d9", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_5.wav": "28680c94450411fc93022830c08c2922257df144b0c22f592915988f741f60ca", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_10_5.wav.meta": "aa6403aa649b2b7d43ccad35915411b5b659fb65ed1a95c6316801764399331e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_2_1.wav": "305c4fe9bc09ac9785fad869420a37b0f15cdbe62e70d877b58774e15d16722b", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_2_1.wav.meta": "54515a0762846e96bb0edc360ecc5e3ddc570ef03742949242c9b0f7cc564691", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_3_1.wav": "ba409cd9f8d69111bb6a1a376cd56d98e1b88cd93f6e383662fcfb3430205ffc", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_3_1.wav.meta": "b7486818c8bbb7240f6847df04fa901d1411ba13277105be57c8f6cc752986c1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_4_1.wav": "c6a33664da7a8d0ee722fae4c6e7ea3aabc002c6d914a1939f3273d7ce07be64", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_4_1.wav.meta": "e802449958d87aecfd9d1eb5cf4ee78f0fecb4d66584a0197160bf6fdb8cf149", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_4_2.wav": "520c0d11b828f1420c704e42e7b1790902b5fbec4726c369d6272827f36bfc03", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_4_2.wav.meta": "e709a090e093a3525c90bd5df3fca5c3bc0d2068ac3f75f1ee99f147959c459e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_5_1.wav": "d5bcee96fc42217494e525fec51f20cd2f1188070e2e5aeb20b5fc50f58be80f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_5_1.wav.meta": "cb15d69d846645dfb58c73f6a12201bef2bd0b0e9dbb92c0d933a9e07368f796", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_5_2.wav": "b946cda17c260eda8409d97ba13dee9f9b5fdf1f587ec623f047d0cf96a2e8b6", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_5_2.wav.meta": "23203e0d9fb94c44b39dd4c07ec378b03fc0ec62cb768a0200b1b396cc0da9d1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_6_1.wav": "da9e39659a60eaf930f0ac0a7f41ebfc50508893d67ca0d1b7f6a0dfa0f773aa", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_6_1.wav.meta": "62157babc4c09a4f22df86c962ce4c71c0d62a485217467c7fc80ecd2c9218bf", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_6_2.wav": "6b634d5cf24b3d5eca2e9f6ab21519d31ef299b1e0204e9d767a106d9ec3ecf7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_6_2.wav.meta": "e14203226cc3e4e4d641dd527a9f5719321bd1389c4b04375d55a216278c8911", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_6_3.wav": "9dfed6560f52343b57de209194a6342d41da444d1a3f2dcf5dabec7e93d23fa0", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_6_3.wav.meta": "5a551cfbbec6f74a44f78a1866db8d9f988eb414a649e681a43fede0b2d255ab", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_7_1.wav": "89f5600677849bb96c519bd2ef67776a0408df72c6d94d513263ac31193a69c1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_7_1.wav.meta": "6b36d19507a1d9a2f6acd290e568109db7d25f3322a6a7375589bc794482262f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_7_2.wav": "818a8fc291bce3fe7253fbd47b36607e097669ba2a03c6b0262a2647e25e4a16", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_7_2.wav.meta": "33c91f50bcef8b190464bc0bc08ba8b037cff46ebf81034cff6bf98f29c94ea0", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_7_3.wav": "07296ac45990a214efa6fe18c5924456304aa128c364abbe3e00d539d63f7363", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_7_3.wav.meta": "eb994c14db669fb0772a0b128b4e1db1f24802c2489b8d970dfb2b7c9857a9d4", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_1.wav": "eb1e903011bb3c82674c8b30f74683715dd988d22db2085db00fce4db697cfcc", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_1.wav.meta": "1d218fd1b9975e14c8c37682cb03d018c6f939f3805dd6b12c188f975b3e5313", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_2.wav": "c6e2211492e15196fb183e81845fab102f8c9cf1dcb82c29331d24403f9e68e3", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_2.wav.meta": "4b4072d9e54fa8aaf56fcfa975e57a7da0e8f4fc9eed88c99f572235152334b7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_3.wav": "c7cf1eff30dde038774b40d895bb833d9dd9c640730ae679711aa48f453e8d90", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_3.wav.meta": "20661c608a6a6205176012ea499a7efc1429ede524a75e8c5fae96b23a94bac3", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_4.wav": "8c2254bf1d3d7fb773717f9cb006868f8978a91b2d926c49875c29d0af3a4db1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_8_4.wav.meta": "555a78775fdeaec2aae7b722b823dd5d53c68cd282ee893503a35f6add5c22ca", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_1.wav": "c6a2a5a8b15795f40907629aafe639b18b28cdb4d1f574d32ad5324b8db35050", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_1.wav.meta": "4f456b9e08c0dded33ae30d9128617b02d9be592dc51110cde8561fc391d278d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_2.wav": "80f290977d76cf097f1be04e3e8a33f5fc677a6213fa4865b876e25d5933e1e7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_2.wav.meta": "bb37d2fd948344a96325c1925ad5e2c40b3785cb3c049d1899dd2238d432b349", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_3.wav": "c0a32bb1e7845d3b45c48b60ecab903670107ae0018cfd11e6a975f952feefd1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_3.wav.meta": "fd11dd1afe2c673a8684b33df002aa647564f3b9bff5c711f87082a9fbcfe9bc", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_4.wav": "1d2bb033451a248b4a516da13a2e53dc89f4e5590a232ad978896845c8286168", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split_9_4.wav.meta": "e80d31a8edcbfb8b0d4eedd48cdad73af4cac8e868803bc4843a9e3a946b7545", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_uncounted.wav": "00eaa41c80725e45d62cc4f5a6b83ef2fce1a5810a8e74f8d60263e022d18575", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_uncounted.wav.meta": "8f41ae04e9b097b83fe7dc6033473640477585b002710d824df2501efc6f477e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/BallView.cs": "4a577b14c39ef9fb47e8dbc64d22d13f6d3438522fe0b7eaf61b8676f5ec2837", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/BallView.cs.meta": "807faf0e796f33a9e8106ca18c33efa2f6a7841e65af55b1cc197ff3eede605a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardEnvironment.cs": "f2946d9b9ca66d9970c3f523d909170d96f06301cc1ab8b8ef2a060e6f299cc0", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardEnvironment.cs.meta": "56746fd3f9461d3bb0377f3f3270a3d589469c1f78b1d7be088579a03e4c4480", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs": "6ebeefcd7c24bc4bd8147cddc430b4d86f8daf3296a8931ef1253880e25214a5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs.meta": "f27b2aa532ccc921c68f336b700d51793d9b360a65cc887e506c03cf70f3ab1e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/ContactPlacement.cs": "c891dfe3d14c3177e1916587b315b3d50636f7406086e35ccdbe66a761ff4b5a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/ContactPlacement.cs.meta": "b00c57375fa2c06d781b808141942e97f61e321f7993ebe724a570e45eac1d99", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Fonts.meta": "7c2a6083ab8776b52f141988f3de71c075215c80181f133896da6fca99ba2308", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Fonts/NotoSansCJKkr-FirstSlice.asset": "bd66a789f4f0ede93cac08ebae2e620343239d69554df4126d554127538b8489", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Fonts/NotoSansCJKkr-FirstSlice.asset.meta": "2f79c0a2ff26ea8102b0bb62c9e135318c264c46067dcb9ee1913fa26fb5854e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Fonts/NotoSansCJKkr-Regular.otf": "6bcb2a0703aa137e874fc2dffa85f6c21ba9a67fa329e81b8c801663af7e992a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Fonts/NotoSansCJKkr-Regular.otf.meta": "b759c2f33ba8ca1b436ec49b17871ea3d5009a255ce8e68e3b9e7d302d321076", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/GameSound.cs": "bf868753d16839b282814d711ff1f38ad3a8e26985487bb5ca21fa3d54bd6f48", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/GameSound.cs.meta": "07a87b900105b362c304de5454bd609ce8dd1bacbacdf15ad041d287d21c73f3", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/GhostDemonstration.cs": "b79a1627f74aaf86d183a754c619b285104eba247ae810a588c052c94c4500eb", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/GhostDemonstration.cs.meta": "496e5ee6b79aebe8e3cfb4fb36ab7d3f191cd61748ca1db6ec5a6f5fb738b4d4", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/GroupBadge.cs": "892c06ec47633e442dc51f28e3f6882b6cbdebe55eac24a69ea8e1ae35c98f9d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/GroupBadge.cs.meta": "a96898260fc7e70e6c406681f03a8766e7b222aa63f37eff8f691da1bf0f4d15", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials.meta": "1f41c8d430d0819835feaadcc7139d96cc9128492324c13b9768035151472e43", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/BlockArch.mat": "3a8da3d88895a82da83dc9a7b1f7cad650312de02591deb5571dd2b0f41200c8", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/BlockArch.mat.meta": "3c3a1e35d7e0eada07c99fa665fee5a18aa88257b0f92f9ab2ea3db5dd0fe4c1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CloudMoon.mat": "9b2e101df885e78b9450b72c4a3fe4cc3e3b0378baf86be76ba497e71eb6bc3e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CloudMoon.mat.meta": "c0963cc353a50ec1eb7ce1dd67f6b28ae3e596dc2d9bc45aa3181ea60ad589f8", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CloudPuff.mat": "0a63dbe8eefaa8fcac38f300e0199b1747abdf11683d5c81106a3b3df6e8b4c5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CloudPuff.mat.meta": "6264e59a31c12fcf962075094a3c183189b64e69ac9df860d6839d7f6e7cb451", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CloudRainbow.mat": "3e4ac5b656dfb84f11b4f22f9e6b3bc45763aebf767bc5c8dd2fbb6719d9f76f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CloudRainbow.mat.meta": "487f3abb4eb624ceab24eec7dcecdd46cdebfa973268eba9f0c4cf93587e68fa", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CountGold.mat": "941df001007b3e747b3d7484e89e0d288aaf1fb41768430bb75e0dc188322798", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/CountGold.mat.meta": "bb03c4d8981b02696f6e83c90b9b778ee41035a4ae04838f8ded8440d7b44902", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/FaceInk.mat": "142a13b20f6746af2434864f308d0b41395535bdefc21288cfc14187084e8daa", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/FaceInk.mat.meta": "42b5473ae103f270fd0740b8c68c2907adb1643ebb9e5a08c1aa3aeae0bc57ce", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/GardenFlower.mat": "db4ad9607a853d755681b2f923ab58329bd1db21dbe0bbd3ad608f5faeefffd7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/GardenFlower.mat.meta": "af14abbd00e1a68dc3d5b1c0830721d1dcf967e9ef534502d3d66a02f115a51c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/GardenLeaf.mat": "514aa1ffc7a62f332c29fade163f77a47f3d63e5701cba5832c87e6895e77a3d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/GardenLeaf.mat.meta": "6b4097e6c28136ab810cfd8541b63491426f83b270e5c7cbf919d46a7cce00d2", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/GardenMushroom.mat": "0d8099b05f77be9d926ad23b7050eca92bc30ae7d38d1dcc18e365ed7f4b480b", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/GardenMushroom.mat.meta": "8fda5b5e013751d3f566bcf66380c2a1b88ca2eaf7c9a142c0ec7718ffed1500", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/Island.mat": "20b0ef32184fbdd29ec920933d6e0c572ef9a3f54670b7df7c52a934f34de333", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/Island.mat.meta": "87bfec5e623fb07752bad467669559504b4099faca2a5456296d2ba2b5a96b15", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/LightRibbon.mat": "39dccc2ecda6829783e895770b989a51cc6864e2d98611d8f09beeede10ac1a5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/LightRibbon.mat.meta": "380f05b6766fb3f421e293c0eb0f753c36a5b9c14429670329a8a48c2d073848", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/MagnetBlue.mat": "43649bf100681a19f4ba2e777ab3fbaf4159a35e229e9f40f2ee7451fb2321d2", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/MagnetBlue.mat.meta": "f07d502c79c004b9d04aa856048cbf03da6a0e7cfa4f53cc2fd04ce31bfead29", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/NumberOutline.mat": "f028a4c27574331f2d84a66bb8000bde992d357f93fba233f03f30d969aa2282", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/NumberOutline.mat.meta": "7e4e9454223f2c1f661056933d81922a5592df24da3675c37aefd834b222340c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/Pebbles.mat": "92face663cc8caaba73010a59dcde838bfece1f45567633df7ffc7c29c415aa2", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/Pebbles.mat.meta": "d37151fdf4d2cc2dd2a507474980f6e97e1963875a7165b97ebee9bbd69b21ba", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/PlaygroundMint.mat": "900353f1c1756dec0238567ef22c6c067383df882fb5c4ca706556dcbe2da54d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/PlaygroundMint.mat.meta": "a5b4cc539a11039bdfb22d0dccf076abfd1793581f308d6b093d556e84122105", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/ToyCoral.mat": "f9bcb12f8e69deff81050deee91ad413f10b86071b5b593ff0485c445d343749", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/ToyCoral.mat.meta": "e94d66896607f78b3585de171cab6321e3110ef0e7e192fa0dad3e071139e4b1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/ToySteps.mat": "b648bddae1df2293f8bd90c23b6325c25f62dd51f23b5b239963cef33f290a73", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/ToySteps.mat.meta": "ab012a56bfb0024f6d8dbba0bb35bafb4b847f0219f2c90618a49b9c735930c8", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/WorkshopCrystals.mat": "42093594e5667838deed9c5317795137ea60aaf6055324f453a3c30db203ac9c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/WorkshopCrystals.mat.meta": "589218049940045258786613242c836154dc9a560119b74c5ff5ee0a9d4a2fce", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/WorkshopGear.mat": "6b38dfddea22b4e48576891be9e362c9430097c2072cedca628b98d6ec30359c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/WorkshopGear.mat.meta": "2cd5e2025f5806a3f0f5f57b35dcb36068bb1b087b0372e2e1cab37d8add47d5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/WorkshopStar.mat": "c7d684bf7d3806a259ef144723ae4afed8af2c842f1d51fb2f77c73100a62e82", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Materials/WorkshopStar.mat.meta": "c2d67cfd68f24ab9cbb2f459e5e1356211a5bb4992bb1d3657e81c2b39d97c49", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models.meta": "a059fd86f9d8ab894e2621ba1219855701cb81d1134f136539e73e095a9ece91", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/BlockArch.fbx": "b1ff050d9fbd670608aa10e1d08b73958ab2cea881e19f942a04ed8b7cbe861b", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/BlockArch.fbx.meta": "117bb55562d5594bd130d4aa86e9454f1ad279b37c29f8c9038c9eeadc02874a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/CloudMoon.fbx": "4b459067f63c44f235209528af4a54d518fb4fbb853e717c9000dad48d7bc847", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/CloudMoon.fbx.meta": "5a0d323147d151b6fe0b5a750cb79533acf05e1cab91709f641e13b57f55b6d7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/CloudPuff.fbx": "b30b350df8f659e22aac886fce6e93ec212fbab51bbadd6015d505524e344354", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/CloudPuff.fbx.meta": "3ddc039b14c9f67188963c5258639add67e8d55aa17dcf6eb543ab0a23be78f3", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/CloudRainbow.fbx": "0e83d9844040003a121b4a521386c4f7ff6ab78a18c29125d78ca374657e9884", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/CloudRainbow.fbx.meta": "df7d5057eaa0dcc03d880e12e7898dc80cd3f7d2e649f37f3772e7f34a98992e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/GardenFlower.fbx": "34b20a629ae3c2b067b5c8cfdaa6d89b191ad9fe8f85d5eaea6cd28a240615d7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/GardenFlower.fbx.meta": "152cfccee68c7f9ecd5eda550054dd82fa7ff97f6f908f496075bebd026a1292", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/GardenLeaf.fbx": "3de135bc7a5fef6866654061c38634d6bfcc4d7e5f5aaaa4019bbbefa400aea9", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/GardenLeaf.fbx.meta": "31236db8fde4f760247c36f6ce27e4c56b523781a9082ace5f9a70a2245d436e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/GardenMushroom.fbx": "e6cd92801bc0c4995a0d6eead1efd30453fa69e6f6074398e6fe67e72058ce80", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/GardenMushroom.fbx.meta": "d7e699ad2357a726e1c3adb15a48b7e071642e2b6d2a0464e57b7a012956d30a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Island.fbx": "425a81f9ae6ce69dbf73a38b4b929b19872f74777f7c6aa7d84ac7da5f2c465a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Island.fbx.meta": "4c4c9204cfebe1f93ca84361ab9dbcdc491f0703c586032aeb1919210e7bda8e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Pebbles.fbx": "14a28f6ac632c47c1a9483bdc77e8ef8184a7cac8265fff535e8671fe800f060", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Pebbles.fbx.meta": "a77b88ed65acb67d02760053642c4c0a973dd7578cfd89a3d0fbef2e25cca8b5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/ToySteps.fbx": "98f0399b60cbbcde3c53ca38e5d97fb979416400c9882cb3c55113208459c292", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/ToySteps.fbx.meta": "76a12048830a4b38bba1a9286287a98abbfab2b3e33b32d9a2c0e0fc62caa79c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/WorkshopCrystals.fbx": "f5fc07a401cc8e0d4ab5505697de5f6b8a97001cf757a513ecc50259627147b5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/WorkshopCrystals.fbx.meta": "b786ed1d98e613fd7dedb3fefabb9f850e118ada35e27da82a0056b22dfb02be", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/WorkshopGear.fbx": "3310e609caf37c206f62f28c392ac2081a93356dd82857677bc3c512d96a7813", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/WorkshopGear.fbx.meta": "ba65bba9dcf915313bac98b68a4978aa735337f5d21cc5b5fb2c56d3c0a0cb37", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/WorkshopStar.fbx": "48c36cae1d4e4a35ce486757992c4f41f5cdb6a94e8a54d672f88c290692e618", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/WorkshopStar.fbx.meta": "428732c2cdfd985ced0ab3186ce8533468aacd7e38d93eb64b24f4eb3db8aeb6", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs.meta": "ca2788db895e69b7fabc76f2603950037ee0250e5db9e6d9279fca48a207846f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/GameApp.prefab": "d722503dcce59a467a1cd4db2286c1e95e84b5f6f614a03b586d0eec866d8394", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/GameApp.prefab.meta": "248f9572123fca7ee45d9d56358e2e89a67c2a78b4c27cb74c4281170ace1db6", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/GroupBadge.prefab": "ec66def535f920fab2ba641867756ee9519c016d9b7b16c136233d53062e192e", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/GroupBadge.prefab.meta": "b76c78e7133454e8bf325948bcbc9f392c96c10fb4eb5a78b01eefac2109c8d7", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/MagnetFriend.prefab": "2de9d4f7cd65b4178cde35a32da6b6eeb3f81e3d53f8b520b09f4a6ebac9f098", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/MagnetFriend.prefab.meta": "5e31161dac1026b55ff8b8c40fec85f1acdf7f154efb573036cda67c7e99139b", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery.meta": "ed564f9e76e6d693e99b00d8b0f18e63864cb58e822a0480cb193039c594c9ee", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/BlockArch.prefab": "250f7470e2ba7e832e5fc3585345915a1ad1c9182ef8712230fbb7ac04a87a95", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/BlockArch.prefab.meta": "5572e39c13ba4cdf63760957a8240bca000b9e0ffee530abbc3c4851a7a32974", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/CloudMoon.prefab": "726139fa7dd50f3f99e5f9174583bc85cbacd5d5da654850870dad6f5ab5c832", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/CloudMoon.prefab.meta": "aec8ddd9916ca8d7c1bfaf7f6b5ca6a5372a34119770f7bf16835933ad8ec6df", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/CloudPuff.prefab": "62dea540dc831ede0224ed9d61dfb0a61966367d88423bef71882837ebc3582d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/CloudPuff.prefab.meta": "dd91846026bf30763e8497ed0ec8c1b0caae1c49d666639e83420884278cb9da", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/CloudRainbow.prefab": "1f1b7ebc8b795380f98147247090926857c6bbb831778f9c0b969d8b01463b1c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/CloudRainbow.prefab.meta": "34e9b08fa7d42e6c5c7a8150135000679b60a940b23f7756b8c937c7dfb5371f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/GardenFlower.prefab": "e505ec53cc4056a112b22f16606cd025fd6aaa3b34f7d0f45f427214e9b2b938", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/GardenFlower.prefab.meta": "58ea83681053709ddbfdfab0bca2bb47f59fad4ff171b46d4b585f4eadd0835a", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/GardenLeaf.prefab": "827cde31118765c12f5f3941943d42a3a45cd06adbb931074b8cf5074b788f47", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/GardenLeaf.prefab.meta": "91ab44e0cd8cdd5114c98389412c05737016538ac1b7444b0ff1efe2e00b7e0d", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/GardenMushroom.prefab": "991ac63529f56cd751c39e1e2170452b43b157894cba79155f24f620f006acf5", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/GardenMushroom.prefab.meta": "5bfb10ca8abfd9799e579b608c343326482fd120a2a4553ae646f9f8dffe312b", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/Island.prefab": "20763c6440f318b731f2cf4c5a35e830ea547b2bb8346d670f86c4ee7e1d6e4c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/Island.prefab.meta": "712a50247312e191576423d7d2ae808fbcd70b8d60ff1be330f4dee7e04bc2de", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/Pebbles.prefab": "3fe5b6fe8f2c233b3dbdbca4d9a9ea2d81e197ddda17cec7e4f5ca3dcccaf3da", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/Pebbles.prefab.meta": "08cd7af820b59000b3a85182e6c7dfc900098316a5109ca3f5b61a864f66ee73", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/ToySteps.prefab": "3bc8671ddf862163caee66cd766c3c4bd99b815ae93ca0feb479244b04223987", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/ToySteps.prefab.meta": "0d2b1099bb0f347d24958aec2fa8782b3fd51c48558246e1536db23c39557fdc", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/WorkshopCrystals.prefab": "60a4ad67b6f438a03615826d236145c92ce9e906b7eda7a301490d27bad5c845", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/WorkshopCrystals.prefab.meta": "eeac924def9abc8108451dc2953ea01733100041525981b5d77770d331c2e2d6", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/WorkshopGear.prefab": "fd681a1058097b933e84630685b52a0b9c2c3e49e3edc9d417167b02e5d2fe59", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/WorkshopGear.prefab.meta": "a169341b2bd3066f0e07e187c5f2a68758444a741421ecac7422a34cd897329f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/WorkshopStar.prefab": "c82f9f5ac524e6ee7586a664cc929b0420ec55154f049bc5f9a03cbb162acc4c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/Scenery/WorkshopStar.prefab.meta": "d6469e653195714d64aab2e701e761710ae1a08c772ab17aa668b1846526542c", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/SafeAreaPanel.cs": "5726a0c8d7ebedc73106f84aa7f111bd42ec92d2132d7f82cbd9a67950b079d1", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/SafeAreaPanel.cs.meta": "1e5251eb0e7cc4ceee8bb9edf223a5ab6d368ad1a1e8d8f778223bc5aff26e12", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites.meta": "98be287bf6ac2323f98b136c72effb20bf1ab79d00ef6f181cdc516aaa70c865", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face0.asset": "a351d09aef580f8bf3d73401008aa93c7fffb250ea010595824dff15766dfcf6", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face0.asset.meta": "f5613e301fff9c03223664e15a8db068a7f81f1b7642b992814265e8bef6ff36", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face1.asset": "a555b81eb573044b53edc7c57bbc2937ba1e7d789527bae02f97686cce6ff816", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face1.asset.meta": "6fadefb2d906d9f49a0fe756a94136787f8ffd033c006ad66c08e63c6288c568", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face2.asset": "6c8cfcc79bf7ab2781b39303ebd6e723fd0f96aec9f30378c7197bb73720d020", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face2.asset.meta": "ca2f2a3457ef05baeae23ca772e3304aa2c6a3e24eedb563c2bda398825560ec", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face3.asset": "9130d438849dc678504cc939f51c9ea91c5b1dff97b7f7f9248b2f617af4da26", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face3.asset.meta": "ca2b60c0c08de47ebf47018eca26be410fec9f91ecd973c6a80db58668039e9f", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face4.asset": "8325c280f8fd83b1f55102593dc772153a3953a70e818467894d7ac98c9f87c0", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face4.asset.meta": "7cf23edcc41e7415eba57c4aa9ead5990d7352f349377536c43b0172d4ecd693", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face5.asset": "a63967c62e7f1ddebe5cfa073b536992b1e02f2ff3aeacd515dc9cb230dca321", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Sprites/Face5.asset.meta": "fbe31b7a354bd949d46e160f600a021cbf13e79988e8aa55dfc2afc0a39f2046", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Textures.meta": "aa7b7000d0c56dc9f71e811146d1f1f8ccfc3f3f55d47a94eb7da9ec526686fb", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Textures/FaceExpressions.png": "0e3c115619c4ae68cf8859ccbc6d4bfb3a26a1a247b3ddb41327c46829540c98", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/Textures/FaceExpressions.png.meta": "1aadbba03b4a8d91e87c663a2e30ae981a57aa524d4ba653fbef34922f1b4f63", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/VoicePlayer.cs": "4920dd894f407dbe6d20c4eb6370d328ec7c30eece2fdd5e35d45a5e81fb74a3", - "Assets/MagnetismHeroes/UnityRuntime/Presentation/VoicePlayer.cs.meta": "03ae52a503737d680db99709bd712bd82a29f3d97c7cefddc8c1535e19cf64c3", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets.meta": "c148a837438ba800381dbbcc7916fd21c5e5ffef228a9588cd72013eed071ac6", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage01.asset": "9882a93e25a70b8eda53656a500c1200efc8ff1a51b5bc138f0406371545799d", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage01.asset.meta": "d191758be4296341ea14ef824080bffa7b6503c2707b83684c73573e57ff3576", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage02.asset": "737657dc2a8a73fb435c8e9e0f4ca0abe33c2bef939c5b57a7571d2df0f113ea", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage02.asset.meta": "0c4b446bddb0344d5f4100ce68123ee08d75f39f1e883535347b604a756ab3c6", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage03.asset": "cd32f32c67549cfde6428a3baefe3004e0729c2a7c4d93416f7ff1d60f58e622", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage03.asset.meta": "fbb526f0736e1e9a0863f50093a8b0323ae647bb6d10b81cfcfc2289eef1bfe7", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage04.asset": "e3e17be5881fdf25ea309ccc0ef122b0617c348b3c1a8f77f888c637da4b4cd0", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage04.asset.meta": "1bf89aa74b745936305791de5ffefdcfef3dee678e6a15506dd6a3284444db4e", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage05.asset": "7c0fb53d06c73a32efcac26063dfeccdb17eedf257e3eeebbaf93d802ccd5cc2", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage05.asset.meta": "dce6799d114e5f82ddeb662a632db10b145e177600a3475efd049d116d54e245", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage06.asset": "aac367d6c3f388f505aa3a25f2b295d30c7b67a0853875b77477c4f87fa9015d", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage06.asset.meta": "81b5cc34fa2e14288468f6d524ff5423f29cdf453af1a6860f8b92fafdfabeae", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage07.asset": "812f206b917e30b4e11950e959ebc1aee3e22f2e815b5126ef4629a2ff137aa4", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage07.asset.meta": "12ec2ae9d4b3cd4ebdc72f422da385208e994ab0542422c03f29b58eed6b8da4", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage08.asset": "8b64735052d78a785e8e76fb079ef2e68206643b86045dc7f986f0e246d94c12", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage08.asset.meta": "fa7787c13df551525c7a247b0913bd0edc3f9d302a8b1c61aeaa9b7ff4b215b4", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage09.asset": "400f3353afe5928772dd87a0d5361dd7cdd1074e64d5db2347a5c7622784cccd", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage09.asset.meta": "290ea5f8135079a754f9441b7db5a844f96a3880fa160dfdd029cabdfe6699d9", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage10.asset": "7746b8afc229340f13159f1b4a7c829d8f9eb2eccd74fcbde837841adbb95d2c", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage10.asset.meta": "667f87d6f986a344e3bdbb38806a9092b03313153d36512535451cc413183102", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage11.asset": "26f13a567794e20295e9318630b0489718ea054ded0316654791091df24e26f2", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage11.asset.meta": "d96ec6ce25ca5936703f3796d2d5fa946b0014a5b48a0b1d8f624cdc019ff486", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage12.asset": "eb65552aef9bfecef99be50768eddf2a527452f17466b118fc7192bd5f3518a5", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage12.asset.meta": "6b07067267abb134f1f2547ded3b5ecdad3a75a95e391aab4b08ef55983003ba", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage13.asset": "5a6d563e300476d591e0d872802ffec4f5a60bd1b8ab73ec63ae9370e8b463a9", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage13.asset.meta": "2b3abc72f2011fa59d9c4a0779ad6226dcc0120519b1e14ba3be4f8d7aa03093", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage14.asset": "ae9bf566f387858b7b2e557c61bd8f0ce8b61a5b36e8dd882bce7ca6469a0ba2", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage14.asset.meta": "18204258707d0bc2b083c4fa8e73fec77a603e7a50d804ec318f4c220eb11710", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage15.asset": "714cfe769f0d672db29d16586dd9a40643afd3d288c1e5917393cb07f0508135", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage15.asset.meta": "145360f4018881ca8724b752a8f2024005f64064bd4bb142dc57e07e038a2b1a", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage16.asset": "ca16d92aa28be84d4a08c40795cf1469407c51788a6ac4e195a810ea41f04571", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage16.asset.meta": "87a0aeb1f8f50b2dc2137e7ce8c45fab93e741cb66e97b10ad8833a40077d2d9", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage17.asset": "3fc87afbc987382799f0c62986edf42e6de55250e387557620d7b86db6d0c898", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage17.asset.meta": "f2cde45482d15f85beecde6e1ef37a17c4b32fd3d44d7fa09cdb48e298bc422d", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage18.asset": "4a779dbb012b8791202deb192ff21459395dc94a451edb226cc83ec05d52b3d8", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage18.asset.meta": "f8126d37d854fffda38b12d036218c78855ac2ea895460f879e9b0ddd7819da1", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage19.asset": "db69f150e92d3782dc4f34f998b07a5b4ed84257786469bd9b1f2900b8685335", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage19.asset.meta": "d2204076f08b08a6d8c173c7af67d2dfe142c2a048fd8fcb3cacbc17bea8ff8b", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage20.asset": "49c496c2a8c99b7670c12697d8c835f92c6dd46fc818c8c89902d668cc2dbc94", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage20.asset.meta": "eef5da97f2f010db711b0fbdd7e68864343d5cd41de8809ff4b0d61c3e2f45fe", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage21.asset": "8afd3c87e8080bd9baf3f2f85276b824f3a3d60c16ca22b7ace5575ee4ef5808", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage21.asset.meta": "397ce3a027feee2145e77951099bb9b920e440a619815036b693a43f80104bb4", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage22.asset": "d94bb8d2b1ab88a75223f71db4a62bc6bc8103c6d8079bfc8205181df16578a8", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage22.asset.meta": "5b7467670a320b3b916c034f167d5726f9ec1b2c6abe3f22250a7138907124ab", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage23.asset": "56bce6a82d4271b8919d733ee475760e43c3f8bf4035e2c1e79096c8dfb22823", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage23.asset.meta": "57761e15155453551024afb329aefd26f9743fbda9f8c4c0dfc30542aa4bbe58", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage24.asset": "98637095636b98dda4e53fe959b8787ab45e63ffa82a5379b0d52010edf6fc3d", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage24.asset.meta": "5d6a6e2aa5c779ec46656bcd4895ef4c6849b657e34ba666b1cfbcf260f44615", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage25.asset": "fbe23e52ef379f1d1009973d42da9c4616370b1a8fb3f2f5adcc5b2f66db6d33", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage25.asset.meta": "6614ddd6e162508445a38ae49e8b14ac6c9b686be3a44ea42f24cd2913554dc4", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage26.asset": "eb36ade8ef35fb89c490df4c75340763bc102e9cb15f98dda1a27e7e3234fc79", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage26.asset.meta": "29e80e3ae35acdd5ea865fa4f39fdcd2974c460d02084b327abe3332826a85ea", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage27.asset": "c14d49d11809e59c8e82d15d257efa9f79b951e2f231cca90147d58b5dcbbe9c", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage27.asset.meta": "025fc99567d27b618db5c7c292afac980db4ed14626a62cfa94f685e66e0839a", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage28.asset": "5c304cf2a5af710b072d9ad3a9ba83613e0a4149dc7d7e3245b2431419300700", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage28.asset.meta": "2bfca1e2842f0ecb24ee94966228058e23729b0709e7755e7c7752bb5eaaea89", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage29.asset": "0a2687bbd6ee2605dffadc50356fd56d9329f54d9713df6882f29d5999447f08", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage29.asset.meta": "3414f5ce22d691417e16217f7d8881f100b966489443a8ec2f48a08acb26f234", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage30.asset": "bd05de078e3625b955b147f12aea1b055d5a8513c98af06fc828e9e5a0bcb840", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/Stage30.asset.meta": "7135b990186907960b62c20cb41c11a7ae881c12adace71995bf29d3b4d44529", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/StageAsset.cs": "83ee09cdf36e27fb293cd2a5ec4cf599096a67044f335028b5070d34763b2793", - "Assets/MagnetismHeroes/UnityRuntime/StageAssets/StageAsset.cs.meta": "38c224d68c81217a9a043ff683882e2593cc19fdc9dda6b67b37ff48a7e3cfa1", - "Assets/Settings.meta": "73c8e19843240273facb0af6bdbf430d719e397aded705629594060ce25ce7ad", - "Assets/Settings/DefaultVolumeProfile.asset": "bda0e0edfaac8afb004c90a2e0fe1b7797d1f2027f329cae5026f5b83727b14f", - "Assets/Settings/DefaultVolumeProfile.asset.meta": "efbcb026cee6b8e32fdf27018c06b0515296a6ccbb43969e792fea270310700a", - "Assets/Settings/Mobile_RPAsset.asset": "839fe528b015841bb60bba04d1c5830ef7b56eb2d78f61692db102de625460f3", - "Assets/Settings/Mobile_RPAsset.asset.meta": "53ea46647712c615fd1a3f2e0dddfa8559eeedc649e9a1dbf93ff913de28544c", - "Assets/Settings/Mobile_Renderer.asset": "aa93274c9521fa941170becec9e87c5fa36b8587eefd5eeb5c0eed0d1d4b1dbe", - "Assets/Settings/Mobile_Renderer.asset.meta": "a148ee2b4987b05b8ff7ae5f9bf84916184f77aefddc821dceb551f5a69f8179", - "Assets/Settings/PC_RPAsset.asset": "65b657b87062616fca959aecbb64b4b01dfd3b0051ac1119fb65be615539df96", - "Assets/Settings/PC_RPAsset.asset.meta": "bb469b9b6f318e9e9bed29c3b2e73e79371c04743c6f95dd6603941f1cd3e53e", - "Assets/Settings/PC_Renderer.asset": "3d0b01d8ded3327263b6690be128a3de58d17c514943ae42fe743a3e084a9c79", - "Assets/Settings/PC_Renderer.asset.meta": "b5df8ddb2708c6ed4c133a5ff9ee95a75cc20e4ee3ed6181366a61aedbaf31ec", - "Assets/Settings/SampleSceneProfile.asset": "a8677d4bac051d24caa11cd622e39f968155ee277bfce132274f1de225692fe9", - "Assets/Settings/SampleSceneProfile.asset.meta": "2e23abc214069f0adbf2b2207f1db54be7876629ec2a3681d0f4aa5b61622ef0", - "Assets/Settings/UniversalRenderPipelineGlobalSettings.asset": "831586bf4833c130db534749c7901260f2aa193ce1a2c3cccfe062a8cf6a38f8", - "Assets/Settings/UniversalRenderPipelineGlobalSettings.asset.meta": "f61661d8bc8dbe531daf09f6211ef978c7ba145c9207c8e76524396ae0722d7c", - "Assets/StreamingAssets.meta": "e7055ff841742399b7e3992ebdc41054bcab446ff7cc9c97d494b3d24e8eafc8", - "Assets/StreamingAssets/Notices.meta": "0ae917dd89ec866c890a0f49f7f587bc89c658a6362057736d317cbfebdbdc1f", - "Assets/StreamingAssets/Notices/NotoSansCJK-OFL.txt": "88f117575237307bdd86a17ef15e21790fc9a662fe4dfb103ca1ca077f0d9982", - "Assets/StreamingAssets/Notices/NotoSansCJK-OFL.txt.meta": "73549968dc8eaf85a156f3f82379ff0a210ed54e4ede31e56b930a1ed0fb628a", - "Assets/StreamingAssets/Notices/TTS-Apache-2.0.txt": "7137274bab133744763a3906ace2a13844c2b34635136fc6d4454c25bfd76eda", - "Assets/StreamingAssets/Notices/TTS-Apache-2.0.txt.meta": "c90234c623bb02c38471fc9f8088e9e9059d6fbf079f4a59d3424d6a0a0de448", - "Assets/StreamingAssets/Notices/TTS-model-card.txt": "2157466302fcaf8e4979d0505aa4b648ad32468aff98b3e964046db05e574f3c", - "Assets/StreamingAssets/Notices/TTS-model-card.txt.meta": "d6ed52dda74a6f3e2b1d0c343c7fc4997613401c6fe7d3aad6853aef0f62d65e", - "Assets/TextMesh Pro.meta": "01c98712be33191460d4eba7720f3b157d163deba149053aade1400d8197d8f9", - "Assets/TextMesh Pro/Fonts.meta": "8c596298d518c27d3f42a08e9849e679a66d62b8e79823a6d3b48525290ed647", - "Assets/TextMesh Pro/Fonts/LiberationSans - OFL.txt": "a130e89b7b2326b599c4b2bbd873eaae63feda407541a2899cb8acb306723de6", - "Assets/TextMesh Pro/Fonts/LiberationSans - OFL.txt.meta": "1b8062370a3bb2be74a46b0ae08688c87cd207cad602fcc77a750cafb106c9af", - "Assets/TextMesh Pro/Fonts/LiberationSans.ttf": "e5b0af421ea2bfbc1ac8d251d647268087ae82786234c57f757d1f0b90fa8b49", - "Assets/TextMesh Pro/Fonts/LiberationSans.ttf.meta": "58603424f5d1f468af3f0c031c288cc9eaa96cb3fb82a0429b16a204a18a5012", - "Assets/TextMesh Pro/Resources.meta": "4bc87da5a85b45fb3252482a603b56e3d34b2174973c527940b225e0b6d9f062", - "Assets/TextMesh Pro/Resources/Fonts & Materials.meta": "124f26d6401f350502d4ea38e1b27353dfbd9905ebf50a1b813cd695c2c4eefe", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Drop Shadow.mat": "73f941d7702c761483bb184fad6e262648032969f0f310344155758b8fad764f", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Drop Shadow.mat.meta": "fa0b8ba96378d1fed663d2206054a5e48139892e3d1c6a6ece97808a6869d70d", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Fallback.asset": "6cd2faa131f346f601036104691f0223744c5806a51ceec454e6d4ff3bb6831d", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Fallback.asset.meta": "0a7cce8a8d21c28bc719e5842c3fb8a3de8eac864a577160d7413d1df08d79d1", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Outline.mat": "4a07f16fcfa697f984d78b7acc005985fe45a256b4bf8b7907faea08075b4961", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Outline.mat.meta": "cb8f2abe533ef11a46ab112cf8dbefb44cb01fa4b7262060a3c480862824d1fb", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF.asset": "0c990817a90dbe8cc2c54e699e187fe4a27986811ba742050885c92c364626a1", - "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF.asset.meta": "eef709d8a9d8b4c781c532712a61aa28f3ef123b902f6b5a8903f0ea61845b50", - "Assets/TextMesh Pro/Resources/LineBreaking Following Characters.txt": "3a73e5ffe2510756dd3c2e982fdc1218b4be82c6e21abde88070d6860e1bc8e6", - "Assets/TextMesh Pro/Resources/LineBreaking Following Characters.txt.meta": "64ef1da1e73f25ba842200e26d43908685c4b9be66856d47103a26c1cae94fa9", - "Assets/TextMesh Pro/Resources/LineBreaking Leading Characters.txt": "62d3f4d5f64aaf885692d2d13a8313f6918e6e4c5aeb3990a43f614652bfd89c", - "Assets/TextMesh Pro/Resources/LineBreaking Leading Characters.txt.meta": "92a36fb59ae49c6974d4ef75b41ffe4c1612110c4b49ed7af5539dcb35e86e8f", - "Assets/TextMesh Pro/Resources/Style Sheets.meta": "9029980ffd3af688190a28cb7f0397a82efe9bd3a8861ed40dfc9018e76d5933", - "Assets/TextMesh Pro/Resources/Style Sheets/Default Style Sheet.asset": "49c813b42ff25580e3934554e3550e7bdb78bcfb2c3562bf1191f0d42f4da141", - "Assets/TextMesh Pro/Resources/Style Sheets/Default Style Sheet.asset.meta": "b652bfc488ebea2ac241cc440ad73c4a9af993c265c800fe0f7cc4917286d0b0", - "Assets/TextMesh Pro/Resources/TMP Settings.asset": "dafd957b416fdad34f611c30ee60d5f603497099f847b83fd688f883a56a3e72", - "Assets/TextMesh Pro/Resources/TMP Settings.asset.meta": "7e88f07fd05c1c4f829c55d8d7660d7ac572e906a5d7152857ef12a574d13375", - "Assets/TextMesh Pro/Shaders.meta": "6fc503950579b274e6745f9f18698f6b23d61ae9e3b8f208f023586c609d9d98", - "Assets/TextMesh Pro/Shaders/SDFFunctions.hlsl": "aa8b32660f8bfd9c1166d94539034437bb0332db6062122d074cd8645fbb98f4", - "Assets/TextMesh Pro/Shaders/SDFFunctions.hlsl.meta": "b35c24e7cc242687d24e1b99a0e921259defd305f2c73873173871b62f4c748f", - "Assets/TextMesh Pro/Shaders/TMP_Bitmap-Custom-Atlas.shader": "4fde41f9b01df2f31d54c5ddfa7703ca63ec89bba6615ea439c63e76f84f7b4b", - "Assets/TextMesh Pro/Shaders/TMP_Bitmap-Custom-Atlas.shader.meta": "41009354edccdb35431e595694e97cda8592094bebf607896161c769b66b3591", - "Assets/TextMesh Pro/Shaders/TMP_Bitmap-Mobile.shader": "d90e395a82303e7c048fe708f8cc76d2c4b304d723c942e8c1c85c30a28acdff", - "Assets/TextMesh Pro/Shaders/TMP_Bitmap-Mobile.shader.meta": "162e6113de4ff13e40bb6bc51f46542f5fc338c65facf395264db261e5d59847", - "Assets/TextMesh Pro/Shaders/TMP_Bitmap.shader": "1d88cbc0fd9f6d1189e6883b6a41433397a8d7fa2c6c267664af89f87687f050", - "Assets/TextMesh Pro/Shaders/TMP_Bitmap.shader.meta": "5278837021cc26fc254d06fdd87012bcc7d1e0521bfe068fbf24ef5481c9eff7", - "Assets/TextMesh Pro/Shaders/TMP_SDF Overlay.shader": "09602d3dd3834e7495fa233ee09a32c0af2e072db6f832b882c2b72014c56d6d", - "Assets/TextMesh Pro/Shaders/TMP_SDF Overlay.shader.meta": "1288285d5a6a95bb5405cc789d7f44139772e3ab9c8ec5fe9a919b3878055682", - "Assets/TextMesh Pro/Shaders/TMP_SDF SSD SpaceWarp.shader": "3819a806df61e6edd0835d26f4915c78726a7d563ff5b453e0af4c15302856ad", - "Assets/TextMesh Pro/Shaders/TMP_SDF SSD SpaceWarp.shader.meta": "7dbcf714e8b6525bc79d53b8393f58e6bd7919e4ecadf5ca70f484c85a9bc7a8", - "Assets/TextMesh Pro/Shaders/TMP_SDF SSD.shader": "02651d722125000a33ef3eedb32773038f878f6d8563b3e2b45af1e38859cdf4", - "Assets/TextMesh Pro/Shaders/TMP_SDF SSD.shader.meta": "12bfe855479eca270552d907ee15b8ce54f2276f15af3127bfc592706d4075a7", - "Assets/TextMesh Pro/Shaders/TMP_SDF SpaceWarp.shader": "f6156e7c6ed7146efdb49728a44474f3fad1ea1a848ada09858c7f761a182a86", - "Assets/TextMesh Pro/Shaders/TMP_SDF SpaceWarp.shader.meta": "ab64b44ebbacf053b1de937c83f78c593306988e3e9b77c84d54fff1ed86d4fb", - "Assets/TextMesh Pro/Shaders/TMP_SDF-HDRP LIT.shadergraph": "5d6bc7a7e8c31cf498e54c9828435815f18e044b898139eb9b491f08461f80a1", - "Assets/TextMesh Pro/Shaders/TMP_SDF-HDRP LIT.shadergraph.meta": "a8b46dd48416668b865839947e4f9079b3677e9da00befc5f7fc9fb7fd4b0306", - "Assets/TextMesh Pro/Shaders/TMP_SDF-HDRP UNLIT.shadergraph": "2506122540a83f3fbd7fecc073ca4eb6ed43a58a9d8b59caa135e66580b0c956", - "Assets/TextMesh Pro/Shaders/TMP_SDF-HDRP UNLIT.shadergraph.meta": "a1e19eef571b0f72337600d9758b51749b68a91ce3b25ede90a679c56e73dc7b", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile Masking.shader": "b3eae5be517f376299ed9152d78fbd6879e2c9a8f35a4b9a133b65bb9254ce18", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile Masking.shader.meta": "c4adca1c50f7380adf32fddcf1f37614d0c7829500e42c6247021a391f3d595c", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile Overlay.shader": "2ab40b8067e36d4b6f262bb3485203788b6b0e9c7521bae69cc47b2a05fb2b0a", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile Overlay.shader.meta": "2a3232392e8d038fd478b3f1704b66362b5e763afb50b3f16ac899c888de73ed", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile SSD SpaceWarp.shader": "79499fcf3da028943a71abd4bd4a2b71f9be5c29d88c9fceeab576373c45df5d", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile SSD SpaceWarp.shader.meta": "7392dad6b3a53b4847ee463883b44ed0f03865f582297f43c86a9b3006d299bd", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile SSD.shader": "c8de13a7886eddfc4f6994ea40b4042721f1238eee89a0ff4c29e32d33faefa2", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile SSD.shader.meta": "3e5f543ae95f82c5ed5738585d6dbf54008a98ae9a627d39b0dce9eb3ddb8374", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile SpaceWarp.shader": "17c4c1960194dae24402700c1a7d5f750843f320545d20a80829a0558648e6e4", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile SpaceWarp.shader.meta": "0cb8e3a86972bb1b099be459bd72c00631936ff733a27d7d98737339d6d3face", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile-2-Pass.shader": "e8cb1c858b51ef6e304e0d5b6b3197ea1ff8cfcc70c3ab89ee7d0d7bd499404f", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile-2-Pass.shader.meta": "80f392cea8fca4c1f5d50a334a40f87afc5cacbf1f2ff432a8992a601b39c007", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile.shader": "ea5bb3f5540de2fba59704d7f31382cf884595980e0b99c05a083873b9e26e3b", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile.shader.meta": "58eff2947dcabefbe4161ed7496b9be052221801758589fa4af452e09632b71c", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Surface-Mobile.shader": "0c2db2ea36e79e533e52ab847bc39bebab06285246955a4b2ff6ea546ee7a1b3", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Surface-Mobile.shader.meta": "507add334f0f385e78c2760b73b8cbb4d1bd60347d8bba1dcb38911302924b49", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Surface.shader": "f10ece0fce4433a45f601fd84b79b74ae82b275910be8d9aa7c09390aa58a668", - "Assets/TextMesh Pro/Shaders/TMP_SDF-Surface.shader.meta": "d849ca253d0f8e0124e5a192f921eef2ea411e78c1bf5bac71c28bb7fec59604", - "Assets/TextMesh Pro/Shaders/TMP_SDF-URP Lit.shadergraph": "d7816ac9c8e4c6bfb579b3101c1826c46f333279c96f69f5734160fc8f68e80a", - "Assets/TextMesh Pro/Shaders/TMP_SDF-URP Lit.shadergraph.meta": "3ae03e3081c0353a098954d842ddd5760dfffa17af6bc8963ce3b60801e6b249", - "Assets/TextMesh Pro/Shaders/TMP_SDF-URP Unlit.shadergraph": "73c1cc198de82cb5b7522618d29ffc47a307f0dfb5cb34ab9c86bf9a84d26e4d", - "Assets/TextMesh Pro/Shaders/TMP_SDF-URP Unlit.shadergraph.meta": "6bfd35c29cd78389fb93663f4bc1bb9b1402e0a0d35b67a86094671c72d796af", - "Assets/TextMesh Pro/Shaders/TMP_SDF.shader": "142c6e313f6731976cad18cfb5b283b3942cce97dd6345ac4fe5a7cf572e7fd5", - "Assets/TextMesh Pro/Shaders/TMP_SDF.shader.meta": "e77b8946de1f5eb76f2e1dd47ccaadc84ff67d30904b8f5bcb998203cdd61396", - "Assets/TextMesh Pro/Shaders/TMP_Sprite.shader": "b8606378647fc693029a0ad8ea7c3d162e1bfe1ff54f2f53f4ab158c9ddb6199", - "Assets/TextMesh Pro/Shaders/TMP_Sprite.shader.meta": "16f167e5dcf135fea9c31def18a5f639f9614bcf15d9f3a48986475858c9d7c0", - "Assets/TextMesh Pro/Shaders/TMPro.cginc": "7b5e42e957e80ddac2c42cd49d15dc48c50b97a0e263248be8a074d1b3c79543", - "Assets/TextMesh Pro/Shaders/TMPro.cginc.meta": "eb95df25958b25d82e30b5a6c543d368209b4a9af73105282537323f132c9cb8", - "Assets/TextMesh Pro/Shaders/TMPro_Mobile.cginc": "e3d42e5e7c32dad5b41ffb4f03d4e7d9382924aff35d470f46e31d291230385a", - "Assets/TextMesh Pro/Shaders/TMPro_Mobile.cginc.meta": "71adfbbc6aa5e2b27d3ab1317d6ef31b9cb4353dec581490d70de4eebe34db7c", - "Assets/TextMesh Pro/Shaders/TMPro_Properties.cginc": "18d4fa11c9b205459b6bc217b703c5fdea9875d7a022f6394a0dd18ac3cd0be4", - "Assets/TextMesh Pro/Shaders/TMPro_Properties.cginc.meta": "4a35e4e340b3fcf0d84abc3635db52dd4c34bead8e5cd547255ec39f09c9cbf5", - "Assets/TextMesh Pro/Shaders/TMPro_Surface.cginc": "3441f62bfb1f6f186118296f5cfd73e84f84776d0551b5195f18542e459b8f4b", - "Assets/TextMesh Pro/Shaders/TMPro_Surface.cginc.meta": "f155b9d0f2c80151af8c99b9c32835f178b71d150e208af7d1716648e4ca10d9", - "Packages/manifest.json": "2193aa859774807d3550546a26217199f1cae5d93b2754afb0d56b85e9d8b320", - "Packages/packages-lock.json": "f0fa7fd2aebf606379f17d8b5fb9f9580d413d3e561387b236c9c99068bed54b", - "ProjectSettings/AudioManager.asset": "aeedeb3243f07b61dbbe854aa08b5ef6f0b5274dd9852ad9f6129dde3e105a31", - "ProjectSettings/ClusterInputManager.asset": "40e65dd214bf254955d2c8829ca8ce2130fd3510485560ce012e9a1ddd93e623", - "ProjectSettings/DynamicsManager.asset": "bf017b547bb77ce3443dd5287ddf2516a74fd167f4f2ee5d798340694f51776d", - "ProjectSettings/EditorBuildSettings.asset": "5504d1c1ab64c6cd972eeba1c23d5310ebe50c01960c976b150ad2d9bb8c6cfc", - "ProjectSettings/EditorSettings.asset": "bbedcf93c32bca12eaba9ed61189ab13f064217e9f2984a66772719fa73c56b5", - "ProjectSettings/GraphicsSettings.asset": "2bbc2cce157a41d0ac0ad69d529c31f3d3abb0250ed4d6e7a84611c0b6a6a61f", - "ProjectSettings/InputManager.asset": "7a1bb836a1008d26d0860d143e9d9df7ca41731d30a72901b518063819c62df2", - "ProjectSettings/MemorySettings.asset": "1e8379a461b10ae09f3f696911088c16875d63e87ed5f2d95df9e1892703f9fd", - "ProjectSettings/MultiplayerManager.asset": "aba89fd1a1ddad182727ad82727e536d27372e90328a8196da2a4560398983dd", - "ProjectSettings/NavMeshAreas.asset": "80106cfa7e6181a1928b142c075f3e510d527441e3d79bc83590e4ac91e7cd66", - "ProjectSettings/PackageManagerSettings.asset": "f281d96e785df430d30ce0a515f4fe5c625ef44467125879f4b9f4cb087e1578", - "ProjectSettings/Physics2DSettings.asset": "d57a0fe55659c33879e8b70bc01c5ea9cdf082069dab649905c6c5138531e6eb", - "ProjectSettings/PhysicsCoreProjectSettings2D.asset": "bedc5339a832c60dc3f11b84abc4f4384dbfa20b0cd9ad6c444b907e751fc35e", - "ProjectSettings/PresetManager.asset": "29c125923fe6e29f90190d4fce1c82d660e7051546e2c9d02908d18c9cf2436c", - "ProjectSettings/ProjectAuditorSettings.asset": "9e68444b9bda981e2927a98df9edf69ea88362f0c6b7f3c81b39b9d515d2a416", - "ProjectSettings/ProjectSettings.asset": "17eb93d5d705ba05c86e4fe80e0d9e7678313266fdcaec03a41512efc255ea96", - "ProjectSettings/ProjectVersion.txt": "87289075c168cdd875e46a5fc308d15c686987848a70c7ce47faf40196ac0e20", - "ProjectSettings/QualitySettings.asset": "da30e325eae700ccd115f079d586886b460a35bc843c698110072e225720b738", - "ProjectSettings/SceneTemplateSettings.json": "bb9098b3bfcde78d93e264b96f7b77b5430e64e8c57a7aad7f5a5d9c3945e16a", - "ProjectSettings/ShaderGraphSettings.asset": "cdba1e5d65bcf6e3269d76ed3e60b6c21f98322002996642c9d74f1eb89f6b91", - "ProjectSettings/TagManager.asset": "23fc56b484aa3d7edf5914cd3591f7a823ccef5e3f3496f8a51f30388ead63dc", - "ProjectSettings/TimeManager.asset": "1a83e54adbbda7c9f4851103a0c6ab7f6448a3343d4ea5b7620452fa08416ecd", - "ProjectSettings/URPProjectSettings.asset": "596d5c8bf4f957b03684c949bebc252ae1918f25415a1147a00684c4b086efda", - "ProjectSettings/UnityConnectSettings.asset": "7f2112b8c6ea0885dcc72b312c9726ffcd909f2e89d1eead61889274a4e5d1c0", - "ProjectSettings/VFXManager.asset": "f066c6483919961332415ee64204f74fe52f38c02e82892eb02b7a5b2e381f0e", - "ProjectSettings/VersionControlSettings.asset": "e7bda45bf7a394745fee1316e6c71fa676004d591ad1f3aa247fd3e4ceb86071", - "ProjectSettings/XRSettings.asset": "edd2beb4a2e388312b4b7da241aa51070434005d82c59363f1237dd2e567585f" - }, - "waves": [ - [ - "T1", - "T2" - ], - [ - "T3" - ], - [ - "T4" - ], - [ - "T5" - ], - [ - "T6" - ] - ], - "verification_plan": { - "modeling": "Actual Blender create/save/reopen/export/assert/render, Python syntax; capture commands and exit codes. Central Blender execution owner; independent script work in separate worktrees.", - "intermediate_integration": "Reopen and verify both merged asset packs plus confirm pre-existing product file hashes unchanged before human approval. Full game verification follows runtime wiring.", - "completion": [ - "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" - ], - "shared_resources": "Blender/Unity/ADB/recording commands serialized by orchestrator. No candidate adoption before T3 explicit user approval." - }, - "tasks": { - "T1": "completed_model_candidate", - "T2": "completed_model_candidate", - "T3": "completed_explicit_user_approval", - "T4": "completed_merged_independently_reviewed", - "T5": "terrain_continuity_fixed_independently_reviewed", - "T6": "verified_delivered_independently_reviewed" - }, - "model_integration": { - "status": "PASS", - "scope": "Model candidates only; user approval pending", - "checkedUtc": "2026-10-08T02:07:58.003206+09:00", - "featureCommit": "5185d9463163ee4c3ce456a6ec5414cc1448f10b", - "protectedBaseCommit": "58410281e015c6600dc543c4e093fd95c036288c", - "protectedProductFilesUnchanged": 533, - "candidateFilesAdded": 32, - "provenanceResourceHashesChecked": 26, - "commands": [ - { - "command": "Blender --background --python-exit-code 1 --python tools/models/build_hero.py -- --verify-only", - "exitCode": 0, - "log": "Heroes/integrated-verify.log", - "assertion": "ASSERTIONS_OK and REOPEN_AND_FBX_REIMPORT_OK evaluated" - }, - { - "command": "Blender --background --python-exit-code 1 --python tools/models/build_meadow.py -- --verify-only --review-dir artifacts/verification/VisualUpgrade/ModelReview/Meadow", - "exitCode": 0, - "log": "Meadow/integrated-verify.log", - "assertion": "MEADOW_VERIFY_PASS evaluated; 21 assets and 23 resources" - }, - { - "command": "python .dryforge/verify-model-integration.py", - "exitCode": 0, - "assertion": "533 protected product hashes, 32 additions, candidate provenance hashes, clean tree and protected dev checked" - } - ], - "userApproval": "PENDING", - "unityGameplayAndroid": "NOT_RUN; awaits explicit model approval" - }, - "model_approval": { - "status": "APPROVED", - "userResponse": "승인합니다", - "confirmedUtc": "2026-10-08T23:05:56.585939+09:00", - "scope": "All five model groups presented in the immediately preceding review", - "groups": [ - "body", - "knightEquipment", - "clearMeadow", - "flowerHills", - "stoneHills" - ], - "reviewPacketSha256": "4787e5679d1fe940060a889ffee42020258587ffb19eff32696ea6fc2f48cc99", - "reviewPacket": "review-packet.json", - "approvedFiles": [ - { - "kind": "BLENDER_SOURCE", - "relativePath": "../../../../SourceAssets/Models/Heroes/MagneticKnight.blend", - "repositoryRelativePath": "SourceAssets/Models/Heroes/MagneticKnight.blend", - "bytes": 1833499, - "sha256": "7ce1272dfe861aee986609cdb899f0edf8b8f2f0f17269c0eda472c8aa90ee3a" - }, - { - "kind": "BLENDER_SOURCE", - "relativePath": "../../../../SourceAssets/Models/Meadow/MeadowCandidates.blend", - "repositoryRelativePath": "SourceAssets/Models/Meadow/MeadowCandidates.blend", - "bytes": 1350505, - "sha256": "2d95f1797c1a1042e32bb2588d0bde735b6e109a5f947b1f0720c7537bda6ea9" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Heroes/HeroBody.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Heroes/HeroBody.fbx", - "bytes": 60076, - "sha256": "56f3301176c612ebeab3fdaaba5c019674beeb0f727f97ed27dd67e32ff50a40" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Heroes/KnightEquipment.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Heroes/KnightEquipment.fbx", - "bytes": 222764, - "sha256": "a8059a020e79e080635e7326bb91576010cad10c60ee931d95536d47fe76e61c" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/DaisyColony.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/DaisyColony.fbx", - "bytes": 179196, - "sha256": "a646a875d99b7cbcef727723b45c6a1e5d6231255ef100689fe69db2e55af63c" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/DistantRidge.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/DistantRidge.fbx", - "bytes": 67788, - "sha256": "af68a4b08e0696cb0b56acf92bf0753d288ad6ba89fd4f19b183995f4ed6fd0c" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/FarHorizon.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/FarHorizon.fbx", - "bytes": 66924, - "sha256": "fcfb4a768ca33ab9ccd166f7e4b3a96cbc918d1a564f59144b0fab399cb7c179" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/FernCluster.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/FernCluster.fbx", - "bytes": 202508, - "sha256": "9153c91cb71a762c54bc0bc3c5e6fcfe2650185defaf60b5da75e25d78aa4911" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GentleHill.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GentleHill.fbx", - "bytes": 67420, - "sha256": "356d3baa0927264f7807e13f3acb717b0c040ae09b23b8feac67e717142d3a19" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GrassTuftGold.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GrassTuftGold.fbx", - "bytes": 72908, - "sha256": "0b2a3d68909b56ecdd3324503d3a6bc2b2f9dc8e1d173536dfe52ca96e49521f" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GrassTuftMint.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GrassTuftMint.fbx", - "bytes": 72700, - "sha256": "650e02dab0ae3a0f05d036e2d58846cb97a93b77864cbc2c704b4364c26d9962" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/LavenderLupineColony.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/LavenderLupineColony.fbx", - "bytes": 250284, - "sha256": "596266e001d7765634ff749deee33cbece1e5bb69ba693bf8c34da795f4f7bdb" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/LowLimestone.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/LowLimestone.fbx", - "bytes": 47228, - "sha256": "026abf6db9395751e7ad933455bacc96a06fe331a4bb777001ee29bf33f8417d" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MeadowClearing.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MeadowClearing.fbx", - "bytes": 68284, - "sha256": "78c307844de3ef5584aaa0bec3e57500f9115065dd643686473e52632d551a29" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MintShrub.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MintShrub.fbx", - "bytes": 96012, - "sha256": "f501d302845a6df4814076abf76bdd97f5ffd2f0527f2aff236e0b97e78e62da" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MossStoneSteps.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MossStoneSteps.fbx", - "bytes": 155068, - "sha256": "5a1bf51f80abde9a9a9e7a419ad30509ad62beac372244e0aba5681896a01739" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/OpenStoneArch.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/OpenStoneArch.fbx", - "bytes": 34636, - "sha256": "8835e7c96ca842ddece1c1f7c9d168a1eabf4f0555e5a025bf46637969dff243" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/PebbleScatter.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/PebbleScatter.fbx", - "bytes": 32060, - "sha256": "34748a549b40ebe8a53cb57d9638ccbf3e480e8b56453b686c378db8a069da8f" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/PinkCosmosColony.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/PinkCosmosColony.fbx", - "bytes": 153516, - "sha256": "6bfc69131ff2cde84f858aa50e3e11697679073d3cd1d0a39a15f4b02cc3afb6" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/RoundCanopyTree.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/RoundCanopyTree.fbx", - "bytes": 147772, - "sha256": "9a41dcb9cb40e080bf4be6418f68eb5893561d9454e54a79a402f936ceb59757" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/RoundLimestone.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/RoundLimestone.fbx", - "bytes": 48700, - "sha256": "eaff76573039ab66927749569e511d5031e90c9ceb7dd8d7cccd84ab7032ca36" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SageCanopyTree.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SageCanopyTree.fbx", - "bytes": 147772, - "sha256": "26665d557ef176a9fcce24107d19619b9848552538482eae4b35ebd9fbcd0aec" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SageShrub.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SageShrub.fbx", - "bytes": 96108, - "sha256": "fbfed3947cc3e05c82f5660bae9c864576839174c7f72c0cbf0a278d6847eefc" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SlateBoulder.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SlateBoulder.fbx", - "bytes": 49852, - "sha256": "48da619e712ba4ee21c55baea8d93ce656db8a1c30475f0d2cb6bfecfc3b0a84" - }, - { - "kind": "FBX", - "relativePath": "../../../../Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/TallWindTree.fbx", - "repositoryRelativePath": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/TallWindTree.fbx", - "bytes": 116140, - "sha256": "378cdb0fc1d67a980d67d51a469148a6d720a03c9e650b35979bcf98ddf6bcad" - } - ] - }, - "T4_commit": "43b22d67886bc5715d3543fefda2dc24dead299f", - "T4_merge_commit": "2d4b4a88e3c72a501fc84f1c9f7933c73452160d", - "T5_commit": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "T5_merge_commit": "b880cc196c02cac959bc5b195a6856048113903f", - "T5_concerns": [ - "Shadow fixed and pixel-verified", - "Belowground broader underside explicitly accepted as unchanged physical-floor/camera limitation", - "Editor resource limitations retained", - "Capture method advisory corrected before final checks" - ], - "completion": { - "productSourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "record": "artifacts/verification/VisualUpgrade/FinalProduct/completion.json", - "recordSha256": "dd898f331a31fb49545e9c3ee2d1c5b52adca97d907f5cff1b0c21b1e6d429fd", - "fourPhasesExitZero": true, - "productFilesUnchanged": 732, - "editMode": 103, - "playMode": 53, - "androidGates": 226, - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "failedAttemptsPreserved": 1 - }, - "T6_commit": "a906be69a0c1211f01d5a3d1bc7fdcf5554efafb", - "documentation_merge_commit": "da169df7c7c7215ce562f8f512d93fb4df2e39b7", - "post_documentation_product_hashes_unchanged": 732, - "final_followup": { - "reason": "Thin sky/dark gaps beneath far hills in actual orbit captures", - "branch": "dryforge/horizon-seam-fix", - "approvedScope": "Far terrain placement only; model binaries/materials/colliders/camera/rules preserved", - "previousProductEvidence": "artifacts/verification/VisualUpgrade/BeforeHorizonFix/manifest.json", - "fixCommit": "ef03611628cb395d377cf56abd951bbf62cfa29c", - "integratedProductCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "targetedActualExitCodes": { - "EditMode": 0, - "PlayMode": 0 - }, - "targetedEvaluatedCases": { - "EditMode": 7, - "PlayMode": 10 - }, - "scope": "Only FarHorizon visual x/z radius35 to27; unchanged height, scale, geometry, materials and gameplay", - "geometryProof": "0 missing samples across central24m corridors in each of three worlds; actual same-camera images confirm reproduced gap removed", - "premergeReview": ".dryforge/reviews/horizon-fix-conformance.json", - "reviewSha256": "5eba704c16ab4e7319c10aad8e337b8737e974df99966ff4b9d05040f3ac3e22" - }, - "previous_completion": { - "productSourceCommit": "83182e7f3e86926bf130817d401a331829401fac", - "record": "artifacts/verification/VisualUpgrade/FinalProduct/completion.json", - "recordSha256": "587b92a7f4f63986bfa32af647bc184745b5b100deeed2b0ca4b7818ac309414", - "fourPhasesExitZero": true, - "productFilesUnchanged": 732, - "editMode": 103, - "playMode": 52, - "androidGates": 226, - "apkSha256": "BEE8E80E03C4205EDEBF616A18940D58654E9115FDC91EE0A80F47DEA91559EA" - }, - "horizon_documentation_commit": "620c73cbd24d936231bee5cb3469e5cff3424cd9", - "final_integrated_head": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "final_review": { - "status": "approved", - "blockingFindings": 0, - "path": "artifacts/verification/VisualUpgrade/FinalProduct/final-review.json", - "sha256": "f11a355ff4316fadba7d5532c481a5215f7cbf722f6c207d86d0781906a846ea" - }, - "final_user_gate": { - "status": "approved", - "recordedUtc": "2026-10-09T14:14:19.8102344Z", - "userStatement": "완성본을 최종 승인합니다", - "scope": "완성 제품과 변경된 제작·채택·운영·검증 문서", - "branch": "dryforge/hero-meadow", - "approvedHead": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "testedProductSource": "3346eb559bdfba24332feb4d9be4b7104520233e", - "productFilesUnchanged": 732, - "apk": "artifacts/android/MagnetismHeroes.apk", - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "gallery": "artifacts/verification/VisualUpgrade/FinalProduct/index.html", - "finalReview": "artifacts/verification/VisualUpgrade/FinalProduct/final-review.json", - "finalReviewSha256": "F11A355FF4316FADBA7D5532C481A5215F7CBF722F6C207D86D0781906A846EA", - "archive": ".dryforge/002", - "integration": "merged-local-dev", - "unityEditor": "정리 중 에디터 종료나 설정 변경 명령을 실행하지 않음", - "editorObservation": { - "observedUtc": "2026-10-09T14:15:23.2646118Z", - "pipelineInstances": 0, - "runningEditorDetected": false - }, - "integrationEvidence": "artifacts/verification/VisualUpgrade/FinalProduct/dev-integration.json" - }, - "status": "complete", - "integration": { - "status": "merged-local-dev", - "userStatement": "-dev에 병합", - "startedUtc": "2026-10-09T14:27:19.2336807Z", - "finishedUtc": "2026-10-09T14:27:20.7186100Z", - "targetBranch": "dev", - "sourceBranch": "dryforge/hero-meadow", - "approvedFeatureCommit": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "previousDevCommit": "58410281e015c6600dc543c4e093fd95c036288c", - "fetchedOriginDevCommit": "58410281e015c6600dc543c4e093fd95c036288c", - "mergeCommit": "a54ae6fb230b0c87e1a2f08a1ad9e2cc2ceb0d73", - "mergeParents": [ - "58410281e015c6600dc543c4e093fd95c036288c", - "0d992d9dcc94843a72489b454ca4e8137178bc56" - ], - "mergeCommand": "git -c core.autocrlf=false merge --no-ff dryforge/hero-meadow -F artifacts/verification/VisualUpgrade/FinalProduct/dev-merge-message.txt", - "mergeExitCode": 0, - "approvedTreeEqualsMergedTree": true, - "testedProductFilesRawSha256Unchanged": 732, - "verification": "제품 파일과 전체 Git tree가 승인본과 동일하므로 기존 실제 검증 증거 유지; 새 검사를 실행한 것으로 기록하지 않음", - "testedProductSource": "3346eb559bdfba24332feb4d9be4b7104520233e", - "completionEvidence": "artifacts/verification/VisualUpgrade/FinalProduct/completion.json", - "currentBranch": "dev", - "workingTreeClean": true, - "mergedFeatureBranchRemoved": true, - "remotePushExecuted": false - } -} diff --git a/.dryforge/002/execution-support/final-product-checks.ps1 b/.dryforge/002/execution-support/final-product-checks.ps1 deleted file mode 100644 index 673ce0a..0000000 --- a/.dryforge/002/execution-support/final-product-checks.ps1 +++ /dev/null @@ -1,81 +0,0 @@ -param([switch]$ResumeAndroidSmoke) -$ErrorActionPreference = 'Stop' -$projectRoot = 'C:/git/MagnetismHeroes' -Set-Location -LiteralPath $projectRoot -$env:PATH = 'C:/Users/baram/AppData/Local/Unity/bin;' + $env:PATH -$evidence = Join-Path $projectRoot 'artifacts/verification/VisualUpgrade/FinalProduct' -New-Item -ItemType Directory -Path $evidence -Force | Out-Null -$sourceCommit = (& git rev-parse HEAD).Trim() -if ([bool](& git status --porcelain)) { throw 'Final source must be clean.' } -$productPaths = @(& git ls-files Assets Packages ProjectSettings tools) -$before = [ordered]@{} -foreach ($path in $productPaths) { $before[$path] = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash } -$beforePath = Join-Path $evidence 'product-before.json' -if ($ResumeAndroidSmoke) { - $originalBefore = Get-Content -LiteralPath $beforePath -Raw | ConvertFrom-Json -AsHashtable - if (@($productPaths | Where-Object { $originalBefore[$_] -ne $before[$_] }).Count -ne 0) { throw 'Product source differs from the original final-check source.' } -} else { $before | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $beforePath -Encoding utf8 } -$env:MAGNETISM_VISUAL_EVIDENCE = Join-Path $evidence 'EditorCapture' -$env:MAGNETISM_PROOF_COMMIT = $sourceCommit -$env:MAGNETISM_PROOF_DIRTY = 'false' -$record = [ordered]@{sourceCommit=$sourceCommit;startedUtc=[DateTime]::UtcNow.ToString('o');status='running';phases=@();productFiles=$productPaths.Count} -$recordPath = Join-Path $evidence 'completion.json' -$phases = @('EditMode','PlayMode','AndroidBuild','AndroidSmoke') -function Confirm-AndroidReadiness { - $sdkAdb = Join-Path $env:LOCALAPPDATA 'Android/Sdk/platform-tools/adb.exe' - $readiness = [ordered]@{startedUtc=[DateTime]::UtcNow.ToString('o');serial='emulator-5554';adb=$sdkAdb;commands=@();status='running'} - foreach ($arguments in @(@('kill-server'), @('start-server'), @('-s','emulator-5554','get-state'), @('-s','emulator-5554','shell','getprop','sys.boot_completed'))) { - $step = $readiness.commands.Count - $stdout = Join-Path $evidence "adb-readiness-$step.stdout.log" - $stderr = Join-Path $evidence "adb-readiness-$step.stderr.log" - $client = Start-Process -FilePath $sdkAdb -ArgumentList $arguments -WindowStyle Hidden -PassThru -RedirectStandardOutput $stdout -RedirectStandardError $stderr - if (-not $client.WaitForExit(30000)) { Stop-Process -Id $client.Id; throw "ADB readiness command timed out: $arguments" } - $client.Refresh() - $output = (Get-Content -LiteralPath $stdout -Raw -ErrorAction SilentlyContinue) - $readiness.commands += [ordered]@{arguments=$arguments;exitCode=$client.ExitCode;stdout=$stdout;stderr=$stderr;output=$output} - $readiness | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $evidence 'android-readiness.json') -Encoding utf8 - if ($client.ExitCode -ne 0) { throw "ADB readiness command failed: $arguments" } - if ($step -eq 2 -and $output.Trim() -ne 'device') { throw 'Dedicated AVD is not online.' } - if ($step -eq 3 -and $output.Trim() -ne '1') { throw 'Dedicated AVD has not completed boot.' } - } - $readiness.status = 'passed' - $readiness.finishedUtc = [DateTime]::UtcNow.ToString('o') - $readiness | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $evidence 'android-readiness.json') -Encoding utf8 -} -if ($ResumeAndroidSmoke) { - $previous = Get-Content -LiteralPath $recordPath -Raw | ConvertFrom-Json -AsHashtable - $passed = @($previous.phases | Where-Object { $_.phase -ne 'AndroidSmoke' }) - if ($previous.sourceCommit -ne $sourceCommit -or $passed.Count -ne 3 -or @($passed | Where-Object { $_.exitCode -ne 0 -or $_.status -ne 'passed' }).Count -ne 0) { throw 'Cannot resume without three successful phases on this source.' } - $record.startedUtc = $previous.startedUtc - $record.resumedUtc = [DateTime]::UtcNow.ToString('o') - $record.phases = $passed - $record.failedAttempts = @($previous.phases | Where-Object { $_.phase -eq 'AndroidSmoke' }) - $phases = @('AndroidSmoke') -} -try { - foreach ($phase in $phases) { - if ($phase -eq 'AndroidSmoke') { Confirm-AndroidReadiness } - $outerLog = Join-Path $evidence ($phase.ToLowerInvariant() + '-outer.log') - $started = [DateTime]::UtcNow.ToString('o') - Write-Output "Starting $phase at $started" - & pwsh -NoProfile -File tools/verify.ps1 -Phase $phase -Milestone Final *> $outerLog - $actualExit = $LASTEXITCODE - $phaseRecordPath = Join-Path $projectRoot ('artifacts/verification/Final/' + $phase.ToLowerInvariant() + '-evidence.json') - $phaseRecord = Get-Content -LiteralPath $phaseRecordPath -Raw | ConvertFrom-Json - $record.phases += [ordered]@{phase=$phase;command="pwsh -NoProfile -File tools/verify.ps1 -Phase $phase -Milestone Final";startedUtc=$started;finishedUtc=[DateTime]::UtcNow.ToString('o');exitCode=$actualExit;status=$phaseRecord.status;sourceCommit=$phaseRecord.sourceCommit;sourceDirty=$phaseRecord.sourceDirty;sourceDirtyEnd=$phaseRecord.sourceDirtyEnd;outerLog=$outerLog;evidence=$phaseRecordPath} - $record | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $recordPath -Encoding utf8 - if ($actualExit -ne 0 -or $phaseRecord.status -ne 'passed' -or $phaseRecord.sourceCommit -ne $sourceCommit -or $phaseRecord.sourceDirty -or $phaseRecord.sourceDirtyEnd) { throw "$phase failed. See $outerLog" } - Write-Output "$phase exit $actualExit, status $($phaseRecord.status)" - } - $after = [ordered]@{} - foreach ($path in $productPaths) { $after[$path] = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash } - $after | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $evidence 'product-after.json') -Encoding utf8 - $changed = @($productPaths | Where-Object { $before[$_] -ne $after[$_] }) - $record.productHashesUnchanged = $changed.Count -eq 0 - $record.changedProducts = $changed - if ($changed.Count -ne 0 -or [bool](& git status --porcelain) -or (& git rev-parse HEAD).Trim() -ne $sourceCommit) { throw 'Product source changed during final checks.' } - $record.status = 'passed' -} catch { $record.status='failed'; $record.error=$_.Exception.Message; Write-Output $record.error } -finally { $record.finishedUtc=[DateTime]::UtcNow.ToString('o'); $record | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $recordPath -Encoding utf8 } -if ($record.status -ne 'passed') { exit 1 } -Write-Output 'All four final phases passed on the same clean integrated product source.' diff --git a/.dryforge/002/execution-support/preserve-before-horizon-fix.py b/.dryforge/002/execution-support/preserve-before-horizon-fix.py deleted file mode 100644 index 555a4a7..0000000 --- a/.dryforge/002/execution-support/preserve-before-horizon-fix.py +++ /dev/null @@ -1,28 +0,0 @@ -import datetime -import hashlib -import json -import pathlib -import shutil - -root = pathlib.Path('C:/git/MagnetismHeroes').resolve() -backup = root / 'artifacts/verification/VisualUpgrade/BeforeHorizonFix' -assert backup.is_relative_to(root / 'artifacts/verification/VisualUpgrade') -backup.mkdir(exist_ok=True) -sources = [root / 'artifacts/verification/Final', root / 'artifacts/verification/VisualUpgrade/FinalProduct'] -paths = [path for source in sources for path in source.rglob('*') if path.is_file()] -paths += [root / 'artifacts/android/Final/MagnetismHeroes.apk', root / 'artifacts/android/MagnetismHeroes.apk'] -sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest() -records = [] -for source in paths: - target = backup / source.relative_to(root) - target.parent.mkdir(parents=True, exist_ok=True) - expected = sha(source) - if target.exists(): - assert sha(target) == expected, target - else: - shutil.copy2(source, target) - assert sha(target) == expected, target - records.append({'source': source.relative_to(root).as_posix(), 'copy': target.relative_to(backup).as_posix(), 'bytes': source.stat().st_size, 'sha256': expected}) -result = {'status': 'PASS', 'preservedUtc': datetime.datetime.now(datetime.timezone.utc).isoformat(), 'productSourceCommit': '83182e7f3e86926bf130817d401a331829401fac', 'files': records} -(backup / 'manifest.json').write_text(json.dumps(result, indent=2) + '\n', encoding='utf-8') -print(json.dumps({'status': 'PASS', 'preservedFiles': len(records), 'preservedBytes': sum(item['bytes'] for item in records)})) diff --git a/.dryforge/002/execution-support/preserve-failed-smoke-and-prune-stale.py b/.dryforge/002/execution-support/preserve-failed-smoke-and-prune-stale.py deleted file mode 100644 index 5a7aea8..0000000 --- a/.dryforge/002/execution-support/preserve-failed-smoke-and-prune-stale.py +++ /dev/null @@ -1,39 +0,0 @@ -import datetime -import hashlib -import json -import pathlib -import shutil - -root = pathlib.Path('C:/git/MagnetismHeroes').resolve() -final = root / 'artifacts/verification/Final' -visual = root / 'artifacts/verification/VisualUpgrade' -previous = visual / 'PreviousFinal' -failed = visual / 'FinalProduct/FailedAndroidAttempt1' -failed.mkdir(parents=True, exist_ok=True) -start = datetime.datetime.fromisoformat('2026-10-08T15:24:08+00:00').timestamp() -sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest() -copied = [] -for path in final.rglob('*'): - if path.is_file() and path.stat().st_mtime >= start: - target = failed / 'Final' / path.relative_to(final) - target.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(path, target) - assert sha(path) == sha(target) - copied.append({'source': str(path.relative_to(root)), 'copy': str(target.relative_to(failed)), 'sha256': sha(target)}) -for name in ['completion.json', 'androidsmoke-outer.log']: - path = visual / 'FinalProduct' / name - target = failed / name - shutil.copy2(path, target) - assert sha(path) == sha(target) -removed = [] -old = json.loads((previous / 'manifest.json').read_text(encoding='utf-8')) -for entry in old['files']: - path = (root / entry['source']).resolve() - if path.is_relative_to(final.resolve()) and path.is_file() and path.stat().st_mtime < start: - backup = previous / entry['copy'] - assert sha(path) == entry['sha256'] == sha(backup), path - removed.append({'source': entry['source'], 'sha256': entry['sha256'], 'preservedCopy': entry['copy']}) - path.unlink() -record = {'status': 'PASS', 'failedAttemptPreserved': copied, 'stalePreviousFinalFilesRemovedAfterExactBackupProof': removed} -(failed / 'preservation-and-pruning.json').write_text(json.dumps(record, indent=2) + '\n', encoding='utf-8') -print(json.dumps({'status': 'PASS', 'failedAttemptFilesPreserved': len(copied), 'backedUpStaleFilesPruned': len(removed)})) diff --git a/.dryforge/002/execution-support/preserve-horizon-failed-smoke.py b/.dryforge/002/execution-support/preserve-horizon-failed-smoke.py deleted file mode 100644 index 3e3d207..0000000 --- a/.dryforge/002/execution-support/preserve-horizon-failed-smoke.py +++ /dev/null @@ -1,40 +0,0 @@ -import hashlib -import json -import shutil -from pathlib import Path - -root = Path('C:/git/MagnetismHeroes') -record_path = root / 'artifacts/verification/VisualUpgrade/FinalProduct/completion.json' -record = json.loads(record_path.read_text(encoding='utf-8-sig')) -assert record['sourceCommit'] == '3346eb559bdfba24332feb4d9be4b7104520233e' -failed = [phase for phase in record['phases'] if phase['phase'] == 'AndroidSmoke'] -assert len(failed) == 1 and failed[0]['exitCode'] == 1 and failed[0]['status'] == 'failed' -destination = root / 'artifacts/verification/VisualUpgrade/HorizonFailedSmokeAttempt1' -files = list((root / 'artifacts/verification/Final/Fresh30').rglob('*')) -files += [root / relative for relative in [ - 'artifacts/verification/Final/androidsmoke-evidence.json', - 'artifacts/verification/Final/androidsmoke.log', - 'artifacts/verification/Final/android-final-evidence.json', - 'artifacts/verification/VisualUpgrade/FinalProduct/completion.json', - 'artifacts/verification/VisualUpgrade/FinalProduct/androidsmoke-outer.log', - 'artifacts/verification/VisualUpgrade/FinalProduct/android-readiness.json', -]] -manifest = {'status': 'preserved-failed-attempt', 'sourceCommit': record['sourceCommit'], 'phaseExitCode': 1, 'files': []} -for source in sorted(set(path for path in files if path.is_file())): - relative = source.relative_to(root) - target = destination / relative - target.parent.mkdir(parents=True, exist_ok=True) - digest = hashlib.sha256(source.read_bytes()).hexdigest() - if target.exists(): - assert hashlib.sha256(target.read_bytes()).hexdigest() == digest, str(target) - else: - shutil.copy2(source, target) - assert hashlib.sha256(target.read_bytes()).hexdigest() == digest, str(target) - manifest['files'].append({'path': relative.as_posix(), 'sha256': digest, 'bytes': source.stat().st_size}) -(destination / 'manifest.json').write_text(json.dumps(manifest, indent=2) + '\n', encoding='utf-8') -for field in ['evidence', 'outerLog']: - original = Path(failed[0][field]) - failed[0][field + 'OriginalPath'] = str(original) - failed[0][field] = str(destination / original.relative_to(root)) -record_path.write_text(json.dumps(record, indent=2) + '\n', encoding='utf-8') -print(json.dumps({'status': 'preserved', 'files': len(manifest['files']), 'phaseExitCode': 1})) diff --git a/.dryforge/002/execution-support/preserve-previous-final.py b/.dryforge/002/execution-support/preserve-previous-final.py deleted file mode 100644 index def4cf9..0000000 --- a/.dryforge/002/execution-support/preserve-previous-final.py +++ /dev/null @@ -1,35 +0,0 @@ -import hashlib -import json -import shutil -from pathlib import Path -from datetime import datetime, timezone - -root = Path(__file__).resolve().parents[1] -target = root / 'artifacts/verification/VisualUpgrade/PreviousFinal' -assert not target.exists(), 'PreviousFinal already exists; do not overwrite evidence' -target.mkdir(parents=True) -records = [] -for source_dir, name in [(root / 'artifacts/verification/Final', 'verification'), - (root / 'artifacts/android/Final', 'android')]: - for source in sorted(source_dir.rglob('*')): - if not source.is_file(): - continue - dest = target / name / source.relative_to(source_dir) - dest.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(source, dest) - actual = hashlib.sha256(dest.read_bytes()).hexdigest() - assert hashlib.sha256(source.read_bytes()).hexdigest() == actual - records.append({'source': source.relative_to(root).as_posix(), - 'copy': dest.relative_to(target).as_posix(), 'bytes': dest.stat().st_size, 'sha256': actual}) -standard = root / 'artifacts/android/MagnetismHeroes.apk' -if standard.is_file(): - dest = target / 'android/standard-MagnetismHeroes.apk' - shutil.copy2(standard, dest) - actual = hashlib.sha256(dest.read_bytes()).hexdigest() - assert actual == hashlib.sha256(standard.read_bytes()).hexdigest() - records.append({'source': standard.relative_to(root).as_posix(), 'copy': dest.relative_to(target).as_posix(), - 'bytes': dest.stat().st_size, 'sha256': actual}) -assert len(records) > 2500 -(target / 'manifest.json').write_text(json.dumps({'status': 'PREVIOUS_RUN_PRESERVED_NOT_CURRENT_VERIFICATION', - 'preservedUtc': datetime.now(timezone.utc).isoformat(), 'files': records}, indent=2), encoding='utf-8') -print(f'PREVIOUS_FINAL_PRESERVED files={len(records)} bytes={sum(r["bytes"] for r in records)} sha256_all_matched') diff --git a/.dryforge/002/execution-support/record-final-user-gate.py b/.dryforge/002/execution-support/record-final-user-gate.py deleted file mode 100644 index a50a08b..0000000 --- a/.dryforge/002/execution-support/record-final-user-gate.py +++ /dev/null @@ -1,23 +0,0 @@ -import hashlib -import json -from pathlib import Path - -root = Path('C:/git/MagnetismHeroes') -state_path = root / '.dryforge/execution.json' -state = json.loads(state_path.read_text(encoding='utf-8-sig')) -review_path = root / 'artifacts/verification/VisualUpgrade/FinalProduct/final-review.json' -review = json.loads(review_path.read_text(encoding='utf-8-sig')) -assert review['status'] == 'approved' -digest = hashlib.sha256(review_path.read_bytes()).hexdigest() -assert digest == 'f11a355ff4316fadba7d5532c481a5215f7cbf722f6c207d86d0781906a846ea' -state['tasks']['T6'] = 'verified_delivered_independently_reviewed' -state['horizon_documentation_commit'] = '620c73cbd24d936231bee5cb3469e5cff3424cd9' -state['final_integrated_head'] = '0d992d9dcc94843a72489b454ca4e8137178bc56' -state['post_documentation_product_hashes_unchanged'] = 732 -state['final_review'] = {'status': 'approved', 'blockingFindings': 0, - 'path': 'artifacts/verification/VisualUpgrade/FinalProduct/final-review.json', 'sha256': digest} -state['final_user_gate'] = {'status': 'pending', 'scope': '완성 제품과 변경된 제작·채택·검증 문서', - 'gallery': 'artifacts/verification/VisualUpgrade/FinalProduct/index.html', - 'apk': 'artifacts/android/MagnetismHeroes.apk'} -state_path.write_text(json.dumps(state, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') -print(json.dumps({'status': 'verified-and-reviewable', 'finalUserGate': 'pending', 'blockingFindings': 0})) diff --git a/.dryforge/002/execution-support/record-horizon-integration.py b/.dryforge/002/execution-support/record-horizon-integration.py deleted file mode 100644 index d9f7a2c..0000000 --- a/.dryforge/002/execution-support/record-horizon-integration.py +++ /dev/null @@ -1,29 +0,0 @@ -import hashlib -import json -from pathlib import Path - -root = Path('C:/git/MagnetismHeroes') -path = root / '.dryforge/execution.json' -state = json.loads(path.read_text(encoding='utf-8-sig')) -state['tasks']['T5'] = 'terrain_continuity_fixed_independently_reviewed' -state['tasks']['T6'] = 'new_product_four_phase_verification_running' -state['previous_completion'] = state['completion'] -state['completion'] = { - 'status': 'running', - 'productSourceCommit': '3346eb559bdfba24332feb4d9be4b7104520233e', - 'record': 'artifacts/verification/VisualUpgrade/FinalProduct/completion.json', -} -state['final_followup'].update({ - 'fixCommit': 'ef03611628cb395d377cf56abd951bbf62cfa29c', - 'integratedProductCommit': '3346eb559bdfba24332feb4d9be4b7104520233e', - 'targetedActualExitCodes': {'EditMode': 0, 'PlayMode': 0}, - 'targetedEvaluatedCases': {'EditMode': 7, 'PlayMode': 10}, - 'scope': 'Only FarHorizon visual x/z radius35 to27; unchanged height, scale, geometry, materials and gameplay', - 'geometryProof': '0 missing samples across central24m corridors in each of three worlds; actual same-camera images confirm reproduced gap removed', - 'premergeReview': '.dryforge/reviews/horizon-fix-conformance.json', -}) -review = root / '.dryforge/reviews/horizon-fix-conformance.json' -if review.exists(): - state['final_followup']['reviewSha256'] = hashlib.sha256(review.read_bytes()).hexdigest() -path.write_text(json.dumps(state, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') -print(json.dumps({'status': 'recorded', 'newProductVerification': 'running'})) diff --git a/.dryforge/002/execution-support/record-model-approval.py b/.dryforge/002/execution-support/record-model-approval.py deleted file mode 100644 index df05a2a..0000000 --- a/.dryforge/002/execution-support/record-model-approval.py +++ /dev/null @@ -1,28 +0,0 @@ -import hashlib -import json -from pathlib import Path -from datetime import datetime, timezone - -root = Path(__file__).resolve().parents[1] -review = root / 'artifacts/verification/VisualUpgrade/ModelReview' -packet_path = review / 'review-packet.json' -packet = json.loads(packet_path.read_text(encoding='utf-8')) -approved = [r for r in packet['files'] if r['relativePath'].lower().endswith(('.blend', '.fbx'))] -assert len(approved) == 25, len(approved) -for record in approved: - path = (review / record['relativePath']).resolve() - assert path.stat().st_size == record['bytes'] - assert hashlib.sha256(path.read_bytes()).hexdigest() == record['sha256'], str(path) -now = datetime.now(timezone.utc).isoformat() -approval = {'status': 'APPROVED', 'userResponse': '\uc2b9\uc778\ud569\ub2c8\ub2e4', - 'confirmedUtc': now, 'scope': 'All five model groups presented in the immediately preceding review', - 'groups': [g['id'] for g in packet['reviewGroups']], - 'reviewPacketSha256': hashlib.sha256(packet_path.read_bytes()).hexdigest(), - 'reviewPacket': 'review-packet.json', 'approvedFiles': approved} -(review / 'user-approval.json').write_text(json.dumps(approval, ensure_ascii=False, indent=2), encoding='utf-8') -state_path = root / '.dryforge/execution.json' -state = json.loads(state_path.read_text(encoding='utf-8')) -state['tasks']['T3'] = 'completed_explicit_user_approval' -state['model_approval'] = approval -state_path.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding='utf-8') -print('EXPLICIT_USER_APPROVAL_RECORDED; 25 original/model hashes matched; 5 groups approved') diff --git a/.dryforge/002/execution-support/record-new-completion.py b/.dryforge/002/execution-support/record-new-completion.py deleted file mode 100644 index e3b55ae..0000000 --- a/.dryforge/002/execution-support/record-new-completion.py +++ /dev/null @@ -1,27 +0,0 @@ -import hashlib -import json -from pathlib import Path - -root = Path('C:/git/MagnetismHeroes') -state_path = root / '.dryforge/execution.json' -state = json.loads(state_path.read_text(encoding='utf-8-sig')) -proof = root / 'artifacts/verification/VisualUpgrade/FinalProduct' -record = json.loads((proof / 'completion.json').read_text(encoding='utf-8-sig')) -audit = json.loads((proof / 'root-final-product-audit.json').read_text(encoding='utf-8-sig')) -assert record['status'] == 'passed' and audit['status'] == 'PASS' -assert record['sourceCommit'] == audit['testedProductSourceCommit'] -state['completion'] = { - 'productSourceCommit': record['sourceCommit'], - 'record': 'artifacts/verification/VisualUpgrade/FinalProduct/completion.json', - 'recordSha256': hashlib.sha256((proof / 'completion.json').read_bytes()).hexdigest(), - 'fourPhasesExitZero': True, - 'productFilesUnchanged': audit['productFileHashesUnchanged'], - 'editMode': 103, 'playMode': 53, 'androidGates': 226, - 'apkSha256': audit['apkSha256'], - 'failedAttemptsPreserved': audit['failedAttemptsPreserved'], -} -review_path = root / '.dryforge/reviews/horizon-fix-conformance.json' -state['final_followup']['reviewSha256'] = hashlib.sha256(review_path.read_bytes()).hexdigest() -state['tasks']['T6'] = 'new_four_phases_passed_documentation_refresh_running' -state_path.write_text(json.dumps(state, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') -print(json.dumps({'status': 'recorded', 'completion': 'passed', 'finalUserGate': 'pending'})) diff --git a/.dryforge/002/execution-support/reference/cartoon-landscape.png b/.dryforge/002/execution-support/reference/cartoon-landscape.png deleted file mode 100644 index 78573f3..0000000 Binary files a/.dryforge/002/execution-support/reference/cartoon-landscape.png and /dev/null differ diff --git a/.dryforge/002/execution-support/reviews/final-review.json b/.dryforge/002/execution-support/reviews/final-review.json deleted file mode 100644 index 2efb0f1..0000000 --- a/.dryforge/002/execution-support/reviews/final-review.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "status": "approved", - "language": "ko", - "reviewedUtc": "2026-10-08T17:48:13.665069+00:00", - "summary": "독립 전체 네 관점 검토를 확정한다. 기존 전체 219파일 검토와 원경 32파일 delta 검토를 유지하고 새 제품 4단계 원문 및 최종 문서/채택 기록을 대조했다. 차단 0건이며 수용된 한계만 남아 있다.", - "scope": { - "mode": "읽기 전용 전체 검토 + 후속 delta/실제 원문 독립 대조", - "runtimeExecution": false, - "testsBuildUnityAdbBlenderRecordingStarted": false, - "onlyWrittenFile": ".dryforge/reviews/final-review.json", - "base": "58410281e015c6600dc543c4e093fd95c036288c", - "reviewedHead": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "testedProductCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "changedFilesFromProtectedDev": 219, - "latestDocumentationChangedFiles": 10, - "latestDocumentationPaths": [ - "SourceAssets/Models/Heroes/active-adoption.json", - "SourceAssets/Models/Meadow/active-adoption.json", - "docs/asset-register.md", - "docs/content.md", - "docs/engineering-notes.md", - "docs/final-verification.md", - "docs/handoff.md", - "docs/operations.md", - "docs/tracking/decisions/0005-hybrid-assets.md", - "docs/tracking/status.md" - ] - }, - "repositoryChecks": { - "gitRootBefore": "C:/git/MagnetismHeroes", - "HEADBefore": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "cleanBefore": true, - "HEADAfter": "0d992d9dcc94843a72489b454ca4e8137178bc56", - "cleanAfter": true, - "protectedDev": "58410281e015c6600dc543c4e093fd95c036288c", - "rootAndModuleInstructionsUnchanged": true, - "testedProductToCurrentAssetsPackagesProjectSettingsToolsDiff": "", - "beforeAfterRawProductHashCount": 732, - "beforeAfterDictionariesIdentical": true, - "currentRawProductHashesMatched": 732, - "acceptedGeneratedYamlWhitespaceCheckExit": 0, - "defaultCodeDocsWhitespaceCheckExit": 0 - }, - "rubric": [ - { - "lens": "명세 준수", - "criteria": "R01~R10의 영웅 우선 장난감 외형·세 자연 테마·실제 지형 연결·승인 파일 동일성·수량/ID/배치/입력/저장 경계", - "result": "통과. 원래 전체 검토에서 승인 연결·대표 장비 lifecycle·30개 비시각 정의 보존을 확인했다. 유일한 원경 틈 차단 사항은 공통 radius27 배치로 수정되어 실제 A/B 및 최신 전체 화면에서 해결됐다. 전체 외곽/모든 각도 가시성을 보장하지 않는다." - }, - { - "lens": "코드 품질", - "criteria": "기존 상태 소유와 취소·복원 경로 유지, scoped Unity API, 실제 메시/입력 검사 및 최소 변경", - "result": "통과. 원래 GroupBadge 3줄 변경은 기존 수명을 따르고 Core/input/persistence를 변경하지 않는다. 원경 helper는 y/scale/rotation/다른 props를 유지하며 generator와 같은 상수를 사용한다. 새 geometry 검사는 별도 PhysicsScene의 실제 sharedMesh hit이며 게임 collider/board 상태는 불변이다. 엄격한 55개 발견/pick/drag·아치 hole/side·95% label 검사를 유지한다." - }, - { - "lens": "문서 내용·형식", - "criteria": "한국어 자기완결 설명, 현재/과거/미검증 범위 구분, 적절한 상세, 허위 통과·새 불필요 의존 금지", - "result": "통과. 최신 8개 docs는 source3346·103/53/226·새 APK 및 실패 시도/Smoke만 재실행을 실제 원문에 맞게 기술한다. ADR는 선택 이유 2문장으로 제한했다. 과거 83182 결과는 BeforeHorizonFix 이력으로 명시했다. 신규 cross-doc 링크 및 active 3문서 참조는 0이다." - }, - { - "lens": "문서 완전성·소스 교차 확인", - "criteria": "원본/FBX→Unity GUID/재질→실제 증거/APK 해시, 실제 종료/소스/파일 동일성, 운영 API/필수 지침/최종 승인 구분", - "result": "통과. active-adoption 239개 SHA 기록(별도 승인 파일25개 포함 총264개 SHA 노드)·111 GUID/metaSHA가 실제 root 파일과 일치한다. gallery8Editor+3native 상대 링크 모두 유효하고 source/시각/카드수치가 일치한다. 문서 통합 이후 raw 제품732개 바이트가 동일하므로 문서만의 이유로 Unity 검사를 추가 요구하지 않는다. 제품 전체 사용자 최종 승인은 여전히 별도다." - } - ], - "issues": [], - "advisory": [ - { - "id": "기존-지면-아래-시점", - "location": "docs/tracking/findings.md:5", - "observation": "yaw210/pitch−45/distance15에서 새 넓은 지면 뒷면이 원래 Island보다 보드를 더 많이 가림을 실제 A/B로 확인했다. 여섯 first physical hit 동일성이 동일 시각 가림을 증명하지 않는다. 55구 탐색/직접 pick/drag 통과에는 음의 pitch 사례가 있다.", - "disposition": "명시적으로 수용된 이번 범위 제한. 기존 카메라/입력/배치 보존을 우선하고 별도 카메라 UX 결정으로 남긴 기록이 정확하다. 현재 변경의 추가 차단 항목으로 올리지 않는다." - }, - { - "id": "실기기-및-얼굴-가시성-한계", - "location": "docs/tracking/status.md:11; FinalProduct/EditorCapture 캡처 metadata", - "observation": "실제 Stage21 기본 화면의 큰 묶음 얼굴 일부가 앞 구에 물리적으로 가려지며 숫자5와 다른 대표 얼굴은 보인다. Editor texture memory는 폰트/RT 포함이고 resident triangle 수는 꺼진 원래 floor도 포함한다. Camera.Render 시간은 Editor CPU wall time이다. AVD와 합성 viewport는 실제 모바일 GPU/FPS/열/배터리·아이 사용성·iOS를 증명하지 않는다.", - "disposition": "배치와 physical occlusion 보존에 따른 수용 제한·후속 검증 범위. 모든 각도/모든 얼굴 가시성 또는 실기기 성능 합격을 주장하지 않은 현재 문서를 유지한다." - }, - { - "id": "원경-geometry-표본-범위", - "location": "docs/engineering-notes.md; DistantTerrainHasContinuousApronAcrossItsVisibleFootInsteadOfSkyGaps", - "observation": "0 누락 결과는 cardinal corridor lateral ±12m/방사 .25m/횡단2m 표본이며 actual foot 약±15.35m 전체 경계·픽셀·각도 증명이 아니다. FOV14는 비교용 임시 카메라이고 제품 FOV62는 유지한다.", - "disposition": "최종 문서의 표본 범위와 실제 close/full 시각 판단을 함께 근거로 사용한다. 현재 실제 관찰에서 중앙 틈은 해결되었고 양끝 평평한 림은 외곽 silhouette로 판정했다. 추가 차단 사항은 없다." - } - ], - "resolvedIssues": [ - { - "id": "원경-지형-연속성", - "previousStatus": "차단", - "resolution": "ef036 원경 배치 수정 및 3346 실제 전체 4단계 통과/현재 문서 반영", - "evidenceReview": ".dryforge/reviews/horizon-fix-conformance.json", - "reviewSha256": "5eba704c16ab4e7319c10aad8e337b8737e974df99966ff4b9d05040f3ac3e22", - "originalWholeReview": "artifacts/verification/VisualUpgrade/BeforeHorizonFix/final-review.json", - "originalReviewSha256": "2f4269c77224aea872289cf51f76a5f6cdb626ec7af1789a63f5fcfb91a0e5b0" - } - ], - "capturedVerification": { - "phases": [ - { - "phase": "EditMode", - "command": "pwsh -NoProfile -File tools/verify.ps1 -Phase EditMode -Milestone Final", - "outerExit": 0, - "rawExit": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "startedUtc": "2026-10-08T16:56:47.0526983Z", - "finishedUtc": "2026-10-08T16:57:10.801033Z", - "evidenceSha256": "c8e8b452df15fdf1a953d0b679af8f8f36b948668e75e4e6d7f76765b981dc14", - "outerLogSha256": "27b5747f628fe5968282f21fa7eb401c5280ad1dbf83967f45fdee5ac207abc2" - }, - { - "phase": "PlayMode", - "command": "pwsh -NoProfile -File tools/verify.ps1 -Phase PlayMode -Milestone Final", - "outerExit": 0, - "rawExit": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "startedUtc": "2026-10-08T16:57:10.8088291Z", - "finishedUtc": "2026-10-08T16:57:54.4619847Z", - "evidenceSha256": "e279aa904d6852115df0204135caef908fdd3e74ae71a8ea8a5827b8c013a41b", - "outerLogSha256": "07316d712aa9e277134cb6a1a406b2718571b358da8a0a2186bd97e2c57f86ef" - }, - { - "phase": "AndroidBuild", - "command": "pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidBuild -Milestone Final", - "outerExit": 0, - "rawExit": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "startedUtc": "2026-10-08T16:57:54.4634842Z", - "finishedUtc": "2026-10-08T16:58:44.8633063Z", - "evidenceSha256": "ac136676b9052f84eeef31d40180e5cd6d91f8516b1e67f08d8134a5addf2dc5", - "outerLogSha256": "f3f115511da8d3e848d705f30789ee4a962ec0eb5709ebb7d7dfbd092e758ead" - }, - { - "phase": "AndroidSmoke", - "command": "pwsh -NoProfile -File tools/verify.ps1 -Phase AndroidSmoke -Milestone Final", - "outerExit": 0, - "rawExit": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "startedUtc": "2026-10-08T17:04:22.9959710Z", - "finishedUtc": "2026-10-08T17:19:58.4677196Z", - "evidenceSha256": "0c1cbcd64170c4e3747aa40ea9de51e36dabf9621b13a09f46c974f51a399122", - "outerLogSha256": "50d4a2b0ee8dfe1891a02b236243aec79dec086cdad391f5d8b6bae66390fa1b" - } - ], - "xmlActual": [ - { - "name": "editmode", - "actualCaseCount": 103, - "actualPassed": 103, - "failed": 0, - "skipped": 0, - "inconclusive": 0, - "sha256": "5f5f187799de24fba7ec9db705306c38c86e69d944d61fc56d09d4413a146516" - }, - { - "name": "playmode", - "actualCaseCount": 53, - "actualPassed": 53, - "failed": 0, - "skipped": 0, - "inconclusive": 0, - "sha256": "e8b3c90b13f4887126ef6a7d03711d3c8f48e678b6d5034fa9463ee95427e826" - } - ], - "androidActual": [ - { - "path": "android-final-evidence.json", - "count": 226, - "notPassed": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "sha256": "18ec499dd9d30cd0a00df16012e8e8021e0cbf77a97089611a3954ed5c00aa7b" - }, - { - "path": "Fresh30/android-final-progression-evidence.json", - "count": 151, - "notPassed": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "sha256": "afe9adb99144f811761340586f27fdaff49d677c490f2bea8dad6a67afff066c" - }, - { - "path": "FinalBoundary/android-final-boundary-evidence.json", - "count": 37, - "notPassed": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "sha256": "c3ea05c1b5d1041a3660af6063dfa21090e1d02857ac9e17d7cd72ade2bcf6eb" - }, - { - "path": "RapidBoundary/android-final-rapid-evidence.json", - "count": 39, - "notPassed": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "sha256": "95a415c702a5f407a2fe86194ff22959a0584cb85328011ec83090b1ae5e42fe" - }, - { - "path": "ContentBoundary/android-content-evidence.json", - "count": 69, - "notPassed": 0, - "sourceCommit": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceCommitEnd": "3346eb559bdfba24332feb4d9be4b7104520233e", - "sourceDirty": false, - "sourceDirtyEnd": false, - "apkSha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "sha256": "bf93139c8b749c10c2c562b20888320b586edeaebe2ba6f27ff5bf214755a9e7" - } - ], - "childrenActualExitCodes": [ - 0, - 0, - 0, - 0 - ], - "freshSequentialActualSuccessChecks": 30, - "freshSavePushCommands": 0, - "apkCopies": { - "paths": [ - "artifacts/android/MagnetismHeroes.apk", - "artifacts/android/Final/MagnetismHeroes.apk", - "artifacts/verification/Final/Fresh30/installed-base.apk" - ], - "bytes": 57215937, - "sha256": "DA533F235A9869E5A68F4A244AF9A89E34A8E6FB9E7C7F2F57D830939507B829", - "allThreeIndependentlyMatched": true - }, - "voicePayload": { - "actualApkDecodedRawReportPassed44": true, - "actualRuntimeWavShaPcmFormatIndependentlyRecomputedMatched": 44, - "apkRedeocodeByReviewer": false, - "newRecordingOrListeningByReviewer": false - }, - "noticesZipExactHashes": { - "NotoSansCJK-OFL.txt": "88f117575237307bdd86a17ef15e21790fc9a662fe4dfb103ca1ca077f0d9982", - "TTS-Apache-2.0.txt": "7137274bab133744763a3906ace2a13844c2b34635136fc6d4454c25bfd76eda", - "TTS-model-card.txt": "2157466302fcaf8e4979d0505aa4b648ad32468aff98b3e964046db05e574f3c" - }, - "forbiddenSourceToolCacheApkEntries": 0, - "approvedModelFileHashesMatched": 25, - "materialExactBytesAndValuesMatched": 31, - "stageNonvisualDefinitionsPreserved": 30, - "metadataShaNodesMatched": 264, - "metadataShaRecordsExcludingApproved25": 239, - "guidAndMetaShaMatched": 111, - "latestEditorCaptureCount": 119, - "galleryEditorCards": 8, - "galleryActualAvdCards": 3, - "galleryDeadLinks": 0, - "galleryRemoteLinks": 0, - "latestImagesPersonallyViewed": { - "artifacts/verification/Final/Fresh30/stage-1-enter.png": "24a1fee6f57ed238976dde803184895fbe4900e3ed218fc3064c9743ef9c7e63", - "artifacts/verification/Final/Fresh30/stage-10-next.png": "a17e8540e959fcad55fcde48ccee3654999647e59f7e245839e3872108776fc9", - "artifacts/verification/Final/Fresh30/stage-20-next.png": "79cf0d0bef7671ebc92c2d1cf1f9b557633f7832425b6fc01809944ea8b0b3b3", - "artifacts/verification/VisualUpgrade/FinalProduct/EditorCapture/world11-ten-one-group-tablet-orbitzoom.png": "31e8a98b730295f894f448a2a45e71d246c723354dece8d047013fc55495ebef", - "artifacts/verification/VisualUpgrade/FinalProduct/EditorCapture/world21-single-resupplied-phone-orbitzoom.png": "91683df326dfe70f269b56d02cd2beedd93f6350fb0957995199b1a54c812dad" - }, - "previousWholeReviewImagesKeptAt": "artifacts\\verification\\VisualUpgrade\\BeforeHorizonFix\\final-review.json", - "horizonImagesPersonallyViewed8KeptAt": ".dryforge\\reviews\\horizon-fix-conformance.json" - }, - "failureAndHistoryPreservation": { - "latestFailedAttemptSource": "3346eb559bdfba24332feb4d9be4b7104520233e", - "latestFailedAttemptActualExit": 1, - "koreanObservation": "Fresh30 stage5의 adb shell wm size가 localhost5037 연결에서 약21초 뒤 timeout10060으로 실제 실패했다. 게임 판정/AVD 충돌로 원인을 단정하지 않는다.", - "latestFailedAttemptArchivedFiles": 1257, - "latestFailedAttemptArchiveHashesIndependentlyMatched": 1257, - "previous83182ArchivedFiles": 2311, - "previousArchiveHashesIndependentlyMatched": 2311, - "retry": "같은 source/APK/732제품 바이트에서 AndroidSmoke만 실제 재실행했다. 새 Edit/Play/Build의 선행 통과를 재사용했으며 세 단계를 재실행했다고 주장하지 않는다. 최초 실패를 통과로 바꾸지 않았다.", - "successfulSmokeStartedUtc": "2026-10-08T17:04:22.9959710Z", - "successfulSmokeFinishedUtc": "2026-10-08T17:19:58.4677196Z", - "olderPreviousFinal2517And113FailedAttemptPreservation": "이전 전체 독립 검토의 원문/해시 확인을 그대로 보존한다." - }, - "latestDocumentsRead": [ - "docs/asset-register.md", - "docs/content.md", - "docs/engineering-notes.md", - "docs/final-verification.md", - "docs/handoff.md", - "docs/operations.md", - "docs/tracking/decisions/0005-hybrid-assets.md", - "docs/tracking/status.md" - ], - "harnessRequiredFilesPresent": true, - "reviewerInstructionsRead": [ - "reviewer-prompt.md", - "harness-review.md", - "harness-format.md" - ], - "currentDocsReadRule": "기존 전체 검토에서 root/모듈 AGENTS와 적용 문서를 직접 읽었고 이번 8개 docs 전후 diff와 운영/결정/인수인계 현재 문맥 및 최신 원문을 다시 대조했다. 역사/범위 밖 문서 참조를 광범위 정리할 신규 요구로 만들지 않는다.", - "evidenceSha256": { - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\completion.json": "dd898f331a31fb49545e9c3ee2d1c5b52adca97d907f5cff1b0c21b1e6d429fd", - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\product-before.json": "c8232551e6b814b7eaadc343e95929ece779102b09daad3817253c847643213f", - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\product-after.json": "c8232551e6b814b7eaadc343e95929ece779102b09daad3817253c847643213f", - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\horizon-documentation-result.json": "816b1725a45bd0206c517a2a2a6b71153a9150d519152547872b3d3ce66a3a1b", - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\horizon-documentation-audit.json": "0cc133ea881ccc9a96172edb40ad12ba7ed25ce00eec0b366119018e8aa6203d", - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\documentation-final-evidence-audit.json": "14e62264a22f360f9afda94e1c8660507cda3b27c30d6f9916607889c2819e08", - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\root-final-product-audit.json": "7f1c84d9fa0f593fe72e15e9d3970c6b10fcb329e71adda291cd07e1802a5b49", - "artifacts\\verification\\VisualUpgrade\\FinalProduct\\index.html": "c959897dce94e24ea3d54198864bcc49dcdc58965aac739930920e26194d79cc", - "artifacts\\verification\\Final\\VoicePayload\\apk-voice-payload.json": "a3c1fc9e0ef0170b3fcb60170ae4ca5b9ec640b644fd1f2b00fea591bdc28650", - "SourceAssets\\Models\\Heroes\\active-adoption.json": "3447de26a676872a4f1527dea9c0c642c90cc690569323f3e19554c31ea99cb5", - "SourceAssets\\Models\\Meadow\\active-adoption.json": "8d77a7fce5a847af64bbca8cb54d0997ab728ff5ca8bf0a9b6949f1df233b6b4" - }, - "nextGate": "검토 승인·차단0. 최종 제품 사용자 승인과 dev 통합/원격 push는 이 리뷰가 수행하거나 대체하지 않는다." -} diff --git a/.dryforge/002/execution-support/reviews/horizon-fix-conformance.json b/.dryforge/002/execution-support/reviews/horizon-fix-conformance.json deleted file mode 100644 index 190c3c6..0000000 --- a/.dryforge/002/execution-support/reviews/horizon-fix-conformance.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "status": "approved", - "reviewType": "원경 틈 수정 pre-merge 독립 검토", - "language": "ko", - "reviewedAtUtc": "2026-10-08T16:57:49.857929+00:00", - "scope": { - "baselineCommit": "da169df7c7c7215ce562f8f512d93fb4df2e39b7", - "reviewedCommit": "ef03611628cb395d377cf56abd951bbf62cfa29c", - "worktree": "C:\\git\\MagnetismHeroes\\.dryforge\\worktrees\\T4", - "changedFiles": 32, - "changedStageAssets": 30, - "changedFarHorizonPositionRows": 120, - "runtimeExecutionByReviewer": false, - "permittedWrite": "리뷰 JSON만", - "nextGate": "root 통합 후 새 제품의 전체 4단계 및 갱신 문서/해시 검토는 별도 후속" - }, - "preservedReview": { - "path": "artifacts\\verification\\VisualUpgrade\\BeforeHorizonFix\\final-review.json", - "sha256": "2f4269c77224aea872289cf51f76a5f6cdb626ec7af1789a63f5fcfb91a0e5b0", - "meaning": "기존 전체 네 관점 검토의 결과와 실제 원문 증거를 유지하며 이번 판단은 원경 수정 delta에 적용한다." - }, - "rubric": [ - { - "lens": "요구·코드 품질", - "result": "통과", - "checks": [ - "R07 실제 화면 중앙 원경 하단의 하늘/검은 틈 제거", - "R03/R04 승인된 구 배치·그룹·목표·순서와 수학 유지", - "R06 승인 원본/FBX 25개 및 31개 재질 정확 해시 유지", - "R09 기존 Stage의 FarHorizon x/z 위치만 갱신하는 최소 수정" - ] - }, - { - "lens": "내용 형식", - "result": "통과", - "checks": [ - "commands/result/preservation JSON과 XML이 실패 시도·통과 범위·소스·환경을 구분", - "캡처 metadata에 14도 foot 비교 FOV 명시" - ] - }, - { - "lens": "완전성", - "result": "통과(이번 좁은 범위)", - "checks": [ - "세 세계 actual geometry probe와 실제 화면", - "targeted Edit 7개 및 Play 10개 원문", - "새 전체 4단계를 이미 수행했다고 기록하지 않음" - ] - }, - { - "lens": "교차 참조·원문", - "result": "통과", - "checks": [ - "CLI command/exit 기록과 Unity 원문 종료 코드 및 XML 대조", - "기존 실패 366·trial28 실패6을 녹색으로 바꾸지 않음", - "승인/재질/비시각부 보존 보고서를 실제 파일·git delta와 대조" - ] - } - ], - "issues": [], - "resolvedBlocker": { - "requirement": "R07 자연스러운 지형 연결", - "location": "Assets/MagnetismHeroes/Editor/VisualUpgradeAssets.cs / 30 StageAssets FarHorizon 배치", - "cause": "radius35에서 파란 원경 아래 apron과의 겹침 부족", - "fix": "공통 FarHorizonRadius27 및 scoped Unity Editor API 배치 갱신", - "visualEvidence": [ - "horizon-world11-foot-legacy-radius35.png", - "horizon-world11-foot-current.png", - "world11-ten-one-group-tablet-orbitzoom.png", - "world21-single-resupplied-phone-orbitzoom.png" - ], - "judgment": "동일 카메라 legacy/current close foot 및 원래 지적된 두 전체 화면에서 중앙 sky/검은 slit가 사라졌다. 전체 화면 양끝의 평평한 원경 림은 관찰 범위에서 녹색 apron과 맞닿는 원래 mound 외곽 실루엣이며, 하늘이 발밑으로 관통하는 잔여 floating gap 근거는 없다." - }, - "codeReview": { - "helper": "ApplyHorizonContinuity는 기존 30 Stage의 FarHorizon 네 개를 기대하고 x/z 방향을 정규화하여 공통 반경으로 옮기며 y/scales/rotation/다른 props를 유지한다. 반복 실행 결과가 같고 기존 generator가 같은 상수를 사용한다. StageSignature로 비시각부 보존을 검사한다.", - "newTest": "실제 imported sharedMesh를 별도 LocalPhysics3D Scene의 MeshCollider로 탐색하므로 반경 상수 복사만으로 통과하는 검사가 아니다. 실제 게임 계층에는 collider를 추가하지 않는다. board JSON 및 actual collider array 불변을 종료 시 검사하고 camera/FOV/임시 legacy 배치를 복원한다.", - "serializedDiff": "32개 파일은 scoped helper·Play geometry/capture metadata·30 Stage이며 120개의 FarHorizon x/z 행 외 다른 Stage 필드 변경이 없다. Unity API 저장 형태를 유지한다." - }, - "preservation": { - "approvedBinaryShaMatched": 25, - "materialExactBytesAndColorMatched": 31, - "stagesNonvisualPrefixMatched": 30, - "unchanged": "승인 geometry/FBX/import metadata/재질·초기 구/그룹/ID/수학/카메라/입력/collider/다른 장식 배치", - "independentMethod": "실제 파일 SHA 비교와 baseline→immutable commit 전체 32파일 diff 검토" - }, - "actualVerification": { - "xmlChecks": [ - { - "path": "red-geometry.xml", - "result": "Failed(Child)", - "total": "1", - "passed": "0", - "failed": "1", - "skipped": "0", - "inconclusive": "0", - "actualCaseCount": 1, - "actualPassedCaseCount": 0 - }, - { - "path": "trial28.xml", - "result": "Failed(Child)", - "total": "1", - "passed": "0", - "failed": "1", - "skipped": "0", - "inconclusive": "0", - "actualCaseCount": 1, - "actualPassedCaseCount": 0 - }, - { - "path": "editmode.xml", - "result": "Passed", - "total": "7", - "passed": "7", - "failed": "0", - "skipped": "0", - "inconclusive": "0", - "actualCaseCount": 7, - "actualPassedCaseCount": 7 - }, - { - "path": "playmode.xml", - "result": "Passed", - "total": "10", - "passed": "10", - "failed": "0", - "skipped": "0", - "inconclusive": "0", - "actualCaseCount": 10, - "actualPassedCaseCount": 10 - } - ], - "editUnityExit": 0, - "editCliExit": 0, - "playUnityExit": 0, - "playCliExit": 0, - "redUnityExit": 2, - "redCliExit": 8, - "redMissingSamples": 366, - "trial28UnityExit": 2, - "trial28CliExit": 8, - "trial28MissingSamples": 6, - "initialCompileAttempt": { - "log": "red.log", - "exit": 1, - "evaluatedGeometry": false - }, - "geometryFinalMissingByStage": { - "1": 0, - "11": 0, - "21": 0 - }, - "commandsVerifiedFrom": "commands.json + Unity log COMMAND LINE ARGUMENTS + test completion exit lines + XML actual cases", - "captureInventory": 119, - "personallyViewedNewPngCount": 8, - "all119ViewedClaim": false - }, - "advisories": [ - { - "id": "geometry-sampling-bound", - "severity": "정보", - "finding": "새 probe는 cardinal corridor lateral ±12m, radial 0..실제 배치 거리, step .25m/2m이다. 실제 원경 foot 폭 약 ±15.35m의 전체 경계나 모든 각도를 망라하지 않는다.", - "disposition": "표본 0만으로 전체 외곽 해결을 주장하지 않고, 독립 실제 화면 판정을 함께 근거로 사용했다. 향후 더 넓은 카메라/원경 범위를 도입할 때 probe 범위를 함께 검토한다." - }, - { - "id": "existing-below-ground-camera", - "severity": "정보", - "finding": "기존 자유 Orbit의 지하 각도에서 넓은 clearing의 underside가 더 큰 화면 영역을 가릴 수 있다.", - "disposition": "기존 전체 리뷰의 수용 한계를 유지한다. 이번 변경이 카메라/물리/구 배치를 바꾸지 않으며 모든 각도 가시성을 주장하지 않는다. 별도 UX 결정 범위이다." - }, - { - "id": "existing-device-and-physical-occlusion", - "severity": "정보", - "finding": "합성 Editor CPU/메모리는 실제 단말 FPS/GPU/열/배터리 검증이 아니며, 일부 초기 그룹의 얼굴은 다른 구에 물리적으로 가려질 수 있다.", - "disposition": "원래 리뷰와 문서의 한계를 유지한다. 현재 초기 배열과 label/55 구 발견·선택·드래그 조건을 보존한다. 실제 단말/아동/iOS는 이번 검토 범위 밖이다." - } - ], - "gitChecks": { - "rootBefore": { - "gitRoot": "C:/git/MagnetismHeroes", - "HEAD": "da169df7c7c7215ce562f8f512d93fb4df2e39b7", - "clean": true, - "protectedDev": "58410281e015c6600dc543c4e093fd95c036288c" - }, - "rootAfter": { - "HEAD": "3346eb559bdfba24332feb4d9be4b7104520233e", - "clean": true, - "protectedDev": "58410281e015c6600dc543c4e093fd95c036288c" - }, - "worktreeAfter": { - "HEAD": "ef03611628cb395d377cf56abd951bbf62cfa29c", - "clean": true - }, - "rootHeadChangeExplanation": "pre-merge clear를 parent에게 먼저 전달한 뒤 root가 통합을 진행하여 종료 체크의 root HEAD가 변했다. 본 판정 대상은 immutable ef036 commit이며 root 통합 전체 단계 승인을 뜻하지 않는다." - }, - "evidenceSha256": { - "commands.json": "30aa2370d563444fb5354fd7aad6e4c996baa2d2bbfd5c67499931a314deea5e", - "result.json": "d3b70ae81c29b15582fe77b0c9c6636432c62f68606d39faff1da8d48da2f7a4", - "preservation.json": "cbab404eef5524a053cfca09be50a579f6bae858cfab78dc3dfdc6e2501ddb34", - "red-geometry.xml": "73b8dbd327c7ddc0618f0c2a10befe12033ae8d0a0585eb66d6dcc37d87a6fe3", - "red-geometry.log": "171a631aa96287796c0c795a4d4ef6662efa65a92707e3894930af5731bc94aa", - "trial28.xml": "da2f290364723076609dc513df06fcb454a9a4d75ac575cb56bd0ac94fa37a5b", - "trial28.log": "e7c9148d4e16fa208b77d222eb6f1443dd5820ec9c54e2f63579f98d9c5c1d0d", - "editmode.xml": "bf3826d56e130eb11264035d563029d1d0277f0a5630f56cdb1ffc4100260a3a", - "editmode.log": "ee4b615dc1f07c731d0774d2b958611afa530173b570f70cb0074090567d7d03", - "playmode.xml": "95e3a935c26803c2af29d94b60c5b674b4ad1c61359bfbcadc9a8d196237ea03", - "playmode.log": "4fe5cc4e76e937ab7d3fef7ce67f0c9f5dabc481e52ec08c9311d7b192ffc14a", - "apply27.log": "c306375c2de4b24a88d59d26924005e37baec7157ca91f40f47e414488c00b09" - }, - "personallyViewedImageSha256": { - "horizon-world11-foot-legacy-radius35.png": "d6d23085030e82f21f48dce40aab221d3657c3ce76d9d3606ae1b256676d1944", - "horizon-world11-foot-current.png": "181a9b24cbeb8a0971b4bb2aa0b467899cce40c3116556cb6fa6e848219ba4cb", - "world11-ten-one-group-tablet-orbitzoom.png": "1ecebdd4677377b490f5836a0ae8cabed6308df9d9bd43f12038a5da31b87720", - "world21-single-resupplied-phone-orbitzoom.png": "9096a13e0cf4ccd9bb8e4b982b6278fbe9f648423676583cc98880d9cfc297da", - "horizon-world1-tablet-default.png": "7f977eb18af84fdf5f2825bcc3b65e8a6733776893870950bdef8d62c8406931", - "horizon-world21-phone-orbitzoom.png": "7255763644f98a36a973b3b17752e79ac83d7633cf9969d8058106e415ee3997", - "horizon-world1-foot-current.png": "66cc35a90ac0ab0bde5a5f1fc52e380b994251149e42335ad9a792ed801386c4", - "horizon-world21-foot-current.png": "05dbbf98e8e1961222ed8579f52768134939a6601148002d9246a0761907d2f9" - }, - "boundedConclusion": "원경 수정 delta를 승인하며 차단 0건이다. 원래 전체 검토 결과를 보존하고 새 통합 제품의 전체 네 단계·문서/메타데이터 변경 검토를 별도 후속으로 남긴다. 최종 사용자 승인이나 dev merge/push의 승인이 아니다." -} diff --git a/.dryforge/002/execution-support/reviews/model-conformance-T1.json b/.dryforge/002/execution-support/reviews/model-conformance-T1.json deleted file mode 100644 index 30d3d75..0000000 --- a/.dryforge/002/execution-support/reviews/model-conformance-T1.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "schemaVersion": 1, - "reviewer": "model_conformance independent spec reviewer", - "base": "58410281e015c6600dc543c4e093fd95c036288c", - "reviewDateUtc": "2026-10-07T17:03:27.724783+00:00", - "status": "pass", - "blockers": [], - "readOnlyScope": "Repository and worktrees read-only; Python bytes/SHA/FBX parsing and image viewing only; no Blender/Unity/commit/runtime adoption. Writes limited to .dryforge/reviews evidence.", - "userApproval": "PENDING; this review does not approve runtime adoption", - "runtimeVerification": "NOT_RUN; later approved Unity integration must verify actual interaction, selection, 30-stage mapping and Android", - "task": "T1", - "branch": "dryforge/hero-model", - "commit": "7d7efbe6b6a1a419ac998f37e3e019a75570a9e5", - "worktree": "C:\\git\\MagnetismHeroes\\.dryforge\\worktrees\\T1", - "changedFiles": 7, - "evidenceRoot": "artifacts/verification/VisualUpgrade/ModelReview/Heroes", - "artifacts": [ - { - "path": "SourceAssets/Models/Heroes/MagneticKnight.blend", - "bytes": 1833499, - "sha256": "7ce1272dfe861aee986609cdb899f0edf8b8f2f0f17269c0eda472c8aa90ee3a" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Heroes/HeroBody.fbx", - "bytes": 60076, - "sha256": "56f3301176c612ebeab3fdaaba5c019674beeb0f727f97ed27dd67e32ff50a40" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Heroes/KnightEquipment.fbx", - "bytes": 222764, - "sha256": "a8059a020e79e080635e7326bb91576010cad10c60ee931d95536d47fe76e61c" - } - ], - "hashes": "Source/2 FBX/face atlas/six sprite records independently matched actual bytes", - "preservation": "13 original FBX byte-identical; existing provenance Git blobs unchanged; only 7 added candidate/tool/doc files; no manual meta or runtime wiring", - "findings": [ - { - "spec": "Body mesh/material improvement, radius .5m and root scale1", - "evidence": [ - "tools/models/build_hero.py:238", - "tools/models/build_hero.py:251", - "SourceAssets/Models/Heroes/verification.json", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/body_standalone.png" - ], - "observed": "New smooth body mesh/material authored. Actual FBX 1986 vertices with radii .499999984 to .500000019m; 1 body mesh and 22 equipment meshes, default scale1 and shared origin." - }, - { - "spec": "Rounded open helmet, short blunt sword, round shield; existing 6 expressions, thin blue mask and cape coexist", - "evidence": [ - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/hero_three_quarter.png", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/knight_front.png", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/knight_side.png", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/knight_back.png", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/expression_0_Neutral.png", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/expression_5_Celebrating.png" - ], - "observed": "All 11 actual PNGs viewed. No face/eye/mouth occlusion or visible broken silhouette preventing the later interaction promise. Intentional attachment overlap is not treated as a solid-intersection-zero requirement." - }, - { - "spec": "Editable source, export/reopen/mesh/SHA checks, source-to-game-file provenance; preview-only atlas reuse", - "evidence": [ - "SourceAssets/Models/Heroes/README.md", - "SourceAssets/Models/Heroes/provenance.json", - "SourceAssets/Models/Heroes/verification.json", - "tools/models/build_hero.py:138", - "tools/models/build_hero.py:212", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/build-final.log", - "artifacts/verification/VisualUpgrade/ModelReview/Heroes/verify.log" - ], - "observed": "Actual Blender5.2.2LTS reopen/FBX-import logs, named body/equipment/review collections, m Z-up to -Z-forward/Y-up, no animation. Face/ribbon not included in FBX and preserve source atlas SHA." - } - ], - "remaining": [ - "Full hero plus three-landscape presentation and explicit user model approval are still required before any runtime adoption.", - "One representative hero per group, quantity/ID/collider boundaries and scene selection are later Unity integration checks." - ] -} diff --git a/.dryforge/002/execution-support/reviews/model-conformance-T2.json b/.dryforge/002/execution-support/reviews/model-conformance-T2.json deleted file mode 100644 index b0e3bf3..0000000 --- a/.dryforge/002/execution-support/reviews/model-conformance-T2.json +++ /dev/null @@ -1,602 +0,0 @@ -{ - "schemaVersion": 1, - "reviewer": "model_conformance independent spec reviewer", - "base": "58410281e015c6600dc543c4e093fd95c036288c", - "reviewDateUtc": "2026-10-07T17:03:27.724783+00:00", - "status": "pass", - "blockers": [], - "readOnlyScope": "Repository and worktrees read-only; Python bytes/SHA/FBX parsing and image viewing only; no Blender/Unity/commit/runtime adoption. Writes limited to .dryforge/reviews evidence.", - "userApproval": "PENDING; this review does not approve runtime adoption", - "runtimeVerification": "NOT_RUN; later approved Unity integration must verify actual interaction, selection, 30-stage mapping and Android", - "task": "T2", - "branch": "dryforge/meadow-model", - "commit": "80be80566a309eab8cb333ea9774218c816efc30", - "worktree": "C:\\git\\MagnetismHeroes\\.dryforge\\worktrees\\T2", - "changedFiles": 25, - "evidenceRoot": "artifacts/verification/VisualUpgrade/ModelReview/Meadow", - "artifacts": [ - { - "path": "SourceAssets/Models/Meadow/MeadowCandidates.blend", - "bytes": 1350505, - "sha256": "2d95f1797c1a1042e32bb2588d0bde735b6e109a5f947b1f0720c7537bda6ea9" - }, - { - "path": "tools/models/build_meadow.py", - "bytes": 43245, - "sha256": "f0e7717a5154bf85eeff68639f6f21ca1f79f6d69af8fa0125acf43415f32db1" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/DaisyColony.fbx", - "bytes": 179196, - "sha256": "a646a875d99b7cbcef727723b45c6a1e5d6231255ef100689fe69db2e55af63c" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/DistantRidge.fbx", - "bytes": 67788, - "sha256": "af68a4b08e0696cb0b56acf92bf0753d288ad6ba89fd4f19b183995f4ed6fd0c" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/FarHorizon.fbx", - "bytes": 66924, - "sha256": "fcfb4a768ca33ab9ccd166f7e4b3a96cbc918d1a564f59144b0fab399cb7c179" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/FernCluster.fbx", - "bytes": 202508, - "sha256": "9153c91cb71a762c54bc0bc3c5e6fcfe2650185defaf60b5da75e25d78aa4911" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GentleHill.fbx", - "bytes": 67420, - "sha256": "356d3baa0927264f7807e13f3acb717b0c040ae09b23b8feac67e717142d3a19" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GrassTuftGold.fbx", - "bytes": 72908, - "sha256": "0b2a3d68909b56ecdd3324503d3a6bc2b2f9dc8e1d173536dfe52ca96e49521f" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/GrassTuftMint.fbx", - "bytes": 72700, - "sha256": "650e02dab0ae3a0f05d036e2d58846cb97a93b77864cbc2c704b4364c26d9962" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/LavenderLupineColony.fbx", - "bytes": 250284, - "sha256": "596266e001d7765634ff749deee33cbece1e5bb69ba693bf8c34da795f4f7bdb" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/LowLimestone.fbx", - "bytes": 47228, - "sha256": "026abf6db9395751e7ad933455bacc96a06fe331a4bb777001ee29bf33f8417d" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MeadowClearing.fbx", - "bytes": 68284, - "sha256": "78c307844de3ef5584aaa0bec3e57500f9115065dd643686473e52632d551a29" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MintShrub.fbx", - "bytes": 96012, - "sha256": "f501d302845a6df4814076abf76bdd97f5ffd2f0527f2aff236e0b97e78e62da" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/MossStoneSteps.fbx", - "bytes": 155068, - "sha256": "5a1bf51f80abde9a9a9e7a419ad30509ad62beac372244e0aba5681896a01739" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/OpenStoneArch.fbx", - "bytes": 34636, - "sha256": "8835e7c96ca842ddece1c1f7c9d168a1eabf4f0555e5a025bf46637969dff243" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/PebbleScatter.fbx", - "bytes": 32060, - "sha256": "34748a549b40ebe8a53cb57d9638ccbf3e480e8b56453b686c378db8a069da8f" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/PinkCosmosColony.fbx", - "bytes": 153516, - "sha256": "6bfc69131ff2cde84f858aa50e3e11697679073d3cd1d0a39a15f4b02cc3afb6" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/RoundCanopyTree.fbx", - "bytes": 147772, - "sha256": "9a41dcb9cb40e080bf4be6418f68eb5893561d9454e54a79a402f936ceb59757" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/RoundLimestone.fbx", - "bytes": 48700, - "sha256": "eaff76573039ab66927749569e511d5031e90c9ceb7dd8d7cccd84ab7032ca36" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SageCanopyTree.fbx", - "bytes": 147772, - "sha256": "26665d557ef176a9fcce24107d19619b9848552538482eae4b35ebd9fbcd0aec" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SageShrub.fbx", - "bytes": 96108, - "sha256": "fbfed3947cc3e05c82f5660bae9c864576839174c7f72c0cbf0a278d6847eefc" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/SlateBoulder.fbx", - "bytes": 49852, - "sha256": "48da619e712ba4ee21c55baea8d93ce656db8a1c30475f0d2cb6bfecfc3b0a84" - }, - { - "path": "Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/TallWindTree.fbx", - "bytes": 116140, - "sha256": "378cdb0fc1d67a980d67d51a469148a6d720a03c9e650b35979bcf98ddf6bcad" - } - ], - "directFbxVerification": [ - { - "asset": "DaisyColony", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 3225, - "triangles": 6210, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "Stem", - "LeafSage", - "PetalCream", - "PollenHoney" - ], - "animationAbsent": true - }, - { - "asset": "DistantRidge", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1089, - "triangles": 2174, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "RidgeTeal", - "RidgeLight", - "SoilWarm" - ], - "animationAbsent": true - }, - { - "asset": "FarHorizon", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1089, - "triangles": 2174, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "RidgeFar", - "SoilWarm" - ], - "animationAbsent": true - }, - { - "asset": "FernCluster", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 3906, - "triangles": 7504, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "LeafSage", - "Stem" - ], - "animationAbsent": true - }, - { - "asset": "GentleHill", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1089, - "triangles": 2174, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "GroundMint", - "GroundSage", - "SoilWarm" - ], - "animationAbsent": true - }, - { - "asset": "GrassTuftGold", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1026, - "triangles": 1976, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "GrassGold", - "GrassMint" - ], - "animationAbsent": true - }, - { - "asset": "GrassTuftMint", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1026, - "triangles": 1976, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "GrassGold", - "GrassMint" - ], - "animationAbsent": true - }, - { - "asset": "LavenderLupineColony", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 4836, - "triangles": 9304, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "Stem", - "LeafSage", - "PetalLavender" - ], - "animationAbsent": true - }, - { - "asset": "LowLimestone", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 536, - "triangles": 1068, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "StoneCream", - "StoneLight", - "StoneShade" - ], - "animationAbsent": true - }, - { - "asset": "MeadowClearing", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1089, - "triangles": 2174, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "GroundMint", - "GroundSage", - "SoilWarm" - ], - "animationAbsent": true - }, - { - "asset": "MintShrub", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1524, - "triangles": 2968, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "BarkHoney", - "LeafMint", - "LeafLight", - "LeafShade" - ], - "animationAbsent": true - }, - { - "asset": "MossStoneSteps", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 3267, - "triangles": 6522, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "StoneCream", - "StoneLight", - "StoneShade" - ], - "animationAbsent": true - }, - { - "asset": "OpenStoneArch", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 396, - "triangles": 788, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "StoneCream", - "StoneLight", - "StoneShade" - ], - "animationAbsent": true - }, - { - "asset": "PebbleScatter", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 336, - "triangles": 656, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "StoneCream", - "StoneLight" - ], - "animationAbsent": true - }, - { - "asset": "PinkCosmosColony", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 2685, - "triangles": 5170, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "Stem", - "LeafSage", - "PetalPink", - "PollenHoney" - ], - "animationAbsent": true - }, - { - "asset": "RoundCanopyTree", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 2486, - "triangles": 4840, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "BarkHoney", - "LeafMint", - "LeafLight", - "LeafShade", - "BarkLight" - ], - "animationAbsent": true - }, - { - "asset": "RoundLimestone", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 563, - "triangles": 1122, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "StoneCream", - "StoneLight", - "StoneShade" - ], - "animationAbsent": true - }, - { - "asset": "SageCanopyTree", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 2486, - "triangles": 4840, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "BarkHoney", - "LeafSage", - "LeafLight", - "LeafShade", - "BarkLight" - ], - "animationAbsent": true - }, - { - "asset": "SageShrub", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1524, - "triangles": 2968, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "BarkHoney", - "LeafSage", - "LeafLight", - "LeafShade" - ], - "animationAbsent": true - }, - { - "asset": "SlateBoulder", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 577, - "triangles": 1150, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "StoneBlue", - "StoneLight", - "StoneShade" - ], - "animationAbsent": true - }, - { - "asset": "TallWindTree", - "fbxVersion": 7400, - "meshCount": 1, - "vertices": 1936, - "triangles": 3804, - "floorPivot": true, - "localScaleOne": true, - "localOriginZero": true, - "nativeBoundsMatch": true, - "uvPresent": true, - "finiteNormals": true, - "materialSlots": [ - "BarkHoney", - "PineMint", - "LeafLight", - "LeafShade" - ], - "animationAbsent": true - } - ], - "hashes": "All 23 resource byte counts/SHA and all10 actual PNG SHA with recorded source SHA independently matched", - "preservation": "PlaygroundProps.blend plus 13 original FBX byte-identical; diff25 added candidate/tool/doc files only; no manual meta/runtime wiring", - "findings": [ - { - "spec": "R01 cyan clear daytime / pastel natural cute cartoon coherent with reference", - "evidence": [ - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Preview_ClearMeadow.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Preview_FlowerHills.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Preview_StoneHills.png", - "tools/models/build_meadow.py:376" - ], - "observed": "Three actual overviews viewed: cyan sky, rounded foliage/ground/horizon, pastel flowers and warm soft daylight. Hero and natural scenery use a coherent rounded cartoon direction; no copied reference character/weapon/UI geometry." - }, - { - "spec": "R04 three landscapes; R05 editable detailed nature and unobscured playable center", - "evidence": [ - "tools/models/build_meadow.py:217", - "tools/models/build_meadow.py:249", - "tools/models/build_meadow.py:456", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Closeup_BranchesAndLeaves.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Closeup_PetalsStemsLeaves.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Closeup_OpenArchAndTerraces.png" - ], - "observed": "All10 PNGs independently viewed. Meadow, denser pink/cream/lavender flower hills and rock-heavy hills are distinct. Tree branches/root/canopy/leaves, shrub, individual grass/fern blades, flower petals/stems/leaves, rock planes, rolling ground and open arch are present. Central low-density play area remains readable in candidate composition." - }, - { - "spec": "R06 native blend +21FBX floor pivots/unit scales/UV/material slots/hash/license and original preservation", - "evidence": [ - "SourceAssets/Models/Meadow/provenance.json", - "SourceAssets/Models/Meadow/README.md", - "tools/models/build_meadow.py:550", - "tools/models/build_meadow.py:582", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/source-verification.json", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/verify-only.log", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/run-evidence.json" - ], - "observed": "Source reopened and 21 actual FBX roundtrips are recorded with exit0. Independent FBX byte parsing verified single mesh per file, exact vertex/triangle counts and native bounds, floor minZ0, default scale1/origin0, UV and finite normals, exact named material slots, axis rotation and no animation. All23 source/tool/export records matched SHA/bytes." - }, - { - "spec": "R07 no unintentional dark patches/checker faceting/ground seams harming readability/coherence", - "evidence": [ - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Preview_ClearMeadow.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Preview_FlowerHills.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Preview_StoneHills.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Catalog_RockPlanes.png", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/Closeup_BranchesAndLeaves.png" - ], - "observed": "No unintended black patch/checkerboard or open ground gap across assembled center. Broad intentional rock planes and ordinary foliage/ground shadows are not classified as violations." - }, - { - "spec": "R10 candidate review output and no adoption before explicit user approval", - "evidence": [ - "SourceAssets/Models/Meadow/README.md", - "SourceAssets/Models/Meadow/provenance.json", - "artifacts/verification/VisualUpgrade/ModelReview/Meadow/render-manifest.json" - ], - "observed": "Three overview, four catalog and three detail renders, editable source and exact SHA are available. Metadata marks approval/import/gameplay/mobile PENDING or NOT_RUN. No runtime refs or meta added." - } - ], - "remaining": [ - "Explicit user approval of hero plus all three landscape candidates before any actual-game reference replacement/runtime adoption.", - "30-stage runtime mapping, actual sphere-position preservation, near-prop collider/arch selection, camera/zoom readability and Android performance require later approved integration." - ] -} diff --git a/.dryforge/002/execution-support/reviews/unity-conformance-T4.json b/.dryforge/002/execution-support/reviews/unity-conformance-T4.json deleted file mode 100644 index e736b05..0000000 --- a/.dryforge/002/execution-support/reviews/unity-conformance-T4.json +++ /dev/null @@ -1,375 +0,0 @@ -{ - "task": "T4", - "review": "Independent read-only pinned-commit spec conformance", - "verdict": "specpass", - "status": "PASS", - "reviewedUtc": "2026-10-08T14:49:34.161360+00:00", - "sourceCommit": "43b22d67886bc5715d3543fefda2dc24dead299f", - "baselineCommit": "7c786c5435042f3f4b5e9a4fb370a4350bb2309e", - "worktree": "C:/git/MagnetismHeroes/.dryforge/worktrees/T4", - "blockingFindings": [], - "checks": [ - { - "requirements": [ - "R10", - "R06" - ], - "result": "pass", - "evidence": "All five groups explicitly approved. Review packet SHA verified. All 25 source/FBX SHA values independently match pinned Git blobs. Apply checks approval and hashes before mutation." - }, - { - "requirements": [ - "R02", - "R03" - ], - "result": "pass", - "evidence": "Actual imported body diameter 1, root scale 1, sole SphereCollider radius .5, one equipment bundle, no equipment collider/Animator/rig. Existing six expressions/mask/ribbon/number and count/contact references preserved in raw prefab diffs. Only runtime edit: 3 GroupBadge lines following existing representative body centre and camera." - }, - { - "requirements": [ - "R03" - ], - "result": "pass", - "evidence": "Actual Main test asserts equipment count, position and camera rotation across preview/cancel, merge/split, rotated representative changes and free-play reentry. Existing badge lifecycle deactivates/destroys previous badges; no Core/save/input changes." - }, - { - "requirements": [ - "R04" - ], - "result": "pass", - "evidence": "All 30 nonvisual serialized StageAsset prefixes identical to baseline. New meadow decoration references everywhere; Garden 1-10/Cloud 11-20/Workshop 21-30 retained. Actual Garden free-play and reentry preserve scene theme; existing source-stage environment path unchanged for other themes." - }, - { - "requirements": [ - "R05" - ], - "result": "pass within T4", - "evidence": "Arch/steps use imported nonconvex shared MeshCollider; no blocked BoxCollider arch. Small/distant vegetation has no collider. GameApp raw diff only disables floor renderer and changes camera background; floor physics preserved. No input ray bypass or ball relocation." - }, - { - "requirements": [ - "R06", - "R08" - ], - "result": "pass", - "evidence": "Scoped Editor source uses Unity import/prefab/serialization APIs and excludes initial builders. All 23 FBX meta settings: scale1/fileScale/axisConversion/noAnimation. Imported roots preserved. All 31 material RGB values match portable provenance. All 268 pre-existing meta blobs, 13 legacy FBXs and 289 protected Core/audio/font/face/atlas/mixer/prefab-meta blobs identical to baseline. No runtime UnityEditor, new network/audio/platform/UI feature." - }, - { - "requirements": [ - "T4 verification" - ], - "result": "pass", - "evidence": "Final asset tests 2/2 and actual Main PlayMode 2/2, all failed/skipped/inconclusive 0; raw logs independently report test exit0. Red XML evaluates the same asset tests and fails on missing body/natural decoration refs; green evaluates and passes. Ten final PNGs independently viewed; JSON pinned to clean commit, correct fixture origins and equipment counts." - }, - { - "requirements": [ - "Source integrity" - ], - "result": "pass", - "evidence": "858 current source files independently match pre-final-play exact hashes; git status clean. Pinned Git content comparison:369 raw exact,488 exact CRLF conversions; GroupBadge mixed endings normalize exactly to pinned commit and exact worktree hash matches manifest. Post-final summary records clean same commit/858 hashes matched." - } - ], - "tests": [ - { - "file": "import-red.xml", - "result": "Failed(Child)", - "total": 2, - "passed": 0, - "failed": 2, - "skipped": 0, - "inconclusive": 0, - "startUtc": "2026-10-08 14:10:25Z", - "endUtc": "2026-10-08 14:10:25Z", - "cases": [ - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.AllThirtyStagesConnectNaturalThemesWithOpenNearMeshCollidersAndColliderFreeDistantDecor", - "result": "Failed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.ApprovedBodyAndOneEquipmentAssemblyAreConnectedWithoutChangingSelectionSphere", - "result": "Failed" - } - ] - }, - { - "file": "import-green.xml", - "result": "Passed", - "total": 2, - "passed": 2, - "failed": 0, - "skipped": 0, - "inconclusive": 0, - "startUtc": "2026-10-08 14:16:25Z", - "endUtc": "2026-10-08 14:16:25Z", - "cases": [ - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.AllThirtyStagesConnectNaturalThemesWithOpenNearMeshCollidersAndColliderFreeDistantDecor", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.ApprovedBodyAndOneEquipmentAssemblyAreConnectedWithoutChangingSelectionSphere", - "result": "Passed" - } - ] - }, - { - "file": "import-final.xml", - "result": "Passed", - "total": 2, - "passed": 2, - "failed": 0, - "skipped": 0, - "inconclusive": 0, - "startUtc": "2026-10-08 14:37:23Z", - "endUtc": "2026-10-08 14:37:23Z", - "cases": [ - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.AllThirtyStagesConnectNaturalThemesWithOpenNearMeshCollidersAndColliderFreeDistantDecor", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.ApprovedBodyAndOneEquipmentAssemblyAreConnectedWithoutChangingSelectionSphere", - "result": "Passed" - } - ] - }, - { - "file": "play-final.xml", - "result": "Passed", - "total": 2, - "passed": 2, - "failed": 0, - "skipped": 0, - "inconclusive": 0, - "startUtc": "2026-10-08 14:38:35Z", - "endUtc": "2026-10-08 14:38:36Z", - "cases": [ - { - "name": "MagnetismHeroes.Tests.VisualUpgradePlayTests.ActualMainShowsThreeThemesAndEquipmentFollowsPreviewCancelMergeSplitAndReentry", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradePlayTests.TenSingleGroupsAndOneTenBallGroupRenderActualImportedResourceCounts", - "result": "Passed" - } - ] - } - ], - "visualObservations": [ - "Three default natural themes render imported smooth sphere bodies and open helmets/blunt swords/round shields, readable faces/numbers and intact Korean UI; no visible missing-material pink or missing references.", - "Selection highlight visible; merged two-sphere group has one bundle and split/reentry two bundles. Synthetic ten-ball fixtures show10 versus1 bundles retaining10 balls. Attachment overlap/ordinary neighbouring-ball occlusion is not treated as a zero-intersection violation.", - "Rotated close view keeps gear/face alignment and continuous terrain. Extreme below-ground view obscures board; see T5 follow-up." - ], - "nonblockingFindings": [ - { - "id": "T5-QUALITY-SHADOWS", - "owner": "T5", - "requirements": [ - "R01", - "R07" - ], - "observation": "Default captures lack visible soft contact shadows; existing configuration unchanged. Full lighting/soft-shadow quality acceptance is not claimed by T4." - }, - { - "id": "T5-EXTREME-CAMERA-OCCLUSION", - "owner": "T5", - "requirements": [ - "R05", - "R07" - ], - "evidence": "final/orbit210-pitch-45-distance15.png", - "observation": "Physical terrain underside obscures board at pitch-45/distance15. One view does not establish a regression. T5 must compare original floor/new ground, physical occlusion and allowed alternate views; this review requests no arbitrary camera restrictions or hit bypass." - } - ], - "pendingChecks": [ - "T5 full Edit/Play regression and legacy decoration-count/mesh-name expectation updates", - "T5 actual collider selection, arch hole, collection discovery and below-ground original/new A/B", - "T5 complete R07 quality/lifecycle/camera checklist and soft shadows", - "T6 full Final EditMode/PlayMode/AndroidBuild/AndroidSmoke, installed APK/source/audio hashes and adoption documentation" - ], - "scope": "T4 application and initial contract checks only. T5/T6 outstanding work is not treated as a T4 blocker; no full-quality/APK acceptance claimed. Reviewer did not run Unity/ADB/Blender or modify products.", - "evidenceHashes": [ - { - "path": "artifacts/verification/VisualUpgrade/ModelReview/user-approval.json", - "bytes": 9870, - "sha256": "b539d582aa77d032918bbb580d10d2374f65be4bdd6868432a84ed1d23a46bc5" - }, - { - "path": "artifacts/verification/VisualUpgrade/ModelReview/review-packet.json", - "bytes": 35970, - "sha256": "4787e5679d1fe940060a889ffee42020258587ffb19eff32696ea6fc2f48cc99" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/source-import.json", - "bytes": 20062, - "sha256": "4a8788aff69cd30befe5c20d94e63542ee9ad4a102710bfd7c208a8beb14f9a6" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/source-before-final-play.json", - "bytes": 161828, - "sha256": "0318ab6bd9a5864e75ac51f096687a9011941558c06e6c4a69dc822f5e720076" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/source-after-final-play.json", - "bytes": 195, - "sha256": "1359db7d0cf3448f0e35230315ada0bc1c6b927181f8bf90cf4aef4e4a396089" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/t4-result.json", - "bytes": 22622, - "sha256": "cb8fd5cb61666ecbe1ce7616357b8a067f660859fb0db79d9ac7e987893cd88b" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/import-red.xml", - "bytes": 5944, - "sha256": "dea3629d727730b2ead7d949abf05f82312045e199f2a080860870159d3f8930" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/import-red-editor.log", - "bytes": 1634226, - "sha256": "4f0f89e645e81199a0c668b87959647aa753b65e7e06134232e3de3270021fa7" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/import-green.xml", - "bytes": 4316, - "sha256": "0f138bd95ad09aa3b4bd7c75a1cf580676be3494470508a37fc7e1edc9bb4798" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/import-green-editor.log", - "bytes": 50407, - "sha256": "29a3c8b40a16ae0c751e051936515f27983d2a8423bb7f73ad6ac2d02dc8d21c" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/import-final.xml", - "bytes": 4317, - "sha256": "945b57b4c5dc054e55aef3eb412463163d96e9bdb530074600d1e62eb69aac1d" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/import-final-editor.log", - "bytes": 51431, - "sha256": "1b04061bda7e4eea4b5440c009b028cf1c451a22fe16f584533bb1b15b231529" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/play-final.xml", - "bytes": 4398, - "sha256": "2b841c77377b9549d79170aebd8ec536ac74e8f8c55bd985d105f9d3f0236f98" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/play-final-editor.log", - "bytes": 40236, - "sha256": "7af95099cc70249ec0e3765d13e8e679e1650f92bc97d131eaaba818bea09905" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/apply-03-editor.log", - "bytes": 108743, - "sha256": "940ac55c31f44609ca2954582c08eb6c90a962a1f8792fae169854008d984323" - }, - { - "path": "artifacts/verification/VisualUpgrade/UnityApply/generated-whitespace-check.log", - "bytes": 56623, - "sha256": "07ff4e1b1a8285808cab6b443557a3a5a8d0c2b379ccfe031df7013cf93cc9ef" - } - ], - "captures": [ - { - "name": "merged-single-group.json", - "bytes": 906, - "sha256": "4a13899bb20d15a50732b6f91e427782281c67e3406cd9f4b2716d5049ddf11d" - }, - { - "name": "merged-single-group.png", - "bytes": 185826, - "sha256": "5196638c35963365696f875d1f86d084c1b2aab6abc022b1fde5fbed0bbda464" - }, - { - "name": "orbit210-pitch-45-distance15.json", - "bytes": 1057, - "sha256": "9c2c38a5c2d5fe1f3dba92ba7d009f7366f6468ba73dc0d9f8e8990a368e1888" - }, - { - "name": "orbit210-pitch-45-distance15.png", - "bytes": 169446, - "sha256": "438aef5dd8b0f71349ef4fc86c5b2015d53af7b46404de8f49273891866e8ca2" - }, - { - "name": "orbit95-pitch45-distance6.json", - "bytes": 1054, - "sha256": "6004a3d38432ac30bf18c95099849d9050a295636a8f8453efbdf2f8d53ec17f" - }, - { - "name": "orbit95-pitch45-distance6.png", - "bytes": 285897, - "sha256": "b9125308cd45e80f044e5e8c434e371605c67665d5708e47717848409adbacbb" - }, - { - "name": "selected-two-groups.json", - "bytes": 912, - "sha256": "f4ee5e2088668a701cd222e05c2a9de729a4b3c1cc3e765642572abfd6c3cae4" - }, - { - "name": "selected-two-groups.png", - "bytes": 188246, - "sha256": "e73cfb13d3a56fd89d44a7156c70c147a377133c52e603fe6b0a29fe73cedb42" - }, - { - "name": "split-reentered-two-groups.json", - "bytes": 928, - "sha256": "26a4bd7cdec640e6c19031e3413bbd61c5eb33a58a705df7e88b0383d3af4412" - }, - { - "name": "split-reentered-two-groups.png", - "bytes": 193022, - "sha256": "ab8bbad3929d731ede4648324aee860fa20918da133709f4daf34c987869e0e8" - }, - { - "name": "stage01.json", - "bytes": 918, - "sha256": "0bc3025d16b0ee0aea1eab4a2ad5384ef0b31c3a53a05cecfbff0850fc461de0" - }, - { - "name": "stage01.png", - "bytes": 178823, - "sha256": "73be5d42d42edc4777147c6767d91122f626d6eb7aa4bedd200500fef2728e82" - }, - { - "name": "stage11.json", - "bytes": 920, - "sha256": "7822bd5b12e514c33bf06dc682d93f74e008566d5e91565dff7185e05b3534df" - }, - { - "name": "stage11.png", - "bytes": 187397, - "sha256": "5800767403c97bbfdcf2f594f1a6da0463327115eb63f9fa406d22a8fc8d8186" - }, - { - "name": "stage21.json", - "bytes": 923, - "sha256": "ab795ce5072b4e8b1df554f43824fa3ec6830ea54437885b5e0a856263d6904d" - }, - { - "name": "stage21.png", - "bytes": 174475, - "sha256": "03b07031bbb6b477537a3b3b80574348d60f2e8915d1a37560bbf5c29c1a0188" - }, - { - "name": "ten-one-group.json", - "bytes": 947, - "sha256": "9330c1913eac3cca1082a478cc67a390345eac13e08d0328acd42d8b550fa5f1" - }, - { - "name": "ten-one-group.png", - "bytes": 401560, - "sha256": "ce3383d3d6e3700d41be4783fdabd34b36df2d36a2bee31b22964eb3acef3983" - }, - { - "name": "ten-single-groups.json", - "bytes": 953, - "sha256": "83c9c9d9cf1b27d61fb514c129eff79ceeee668509306c0f36841529c0ca50f3" - }, - { - "name": "ten-single-groups.png", - "bytes": 493264, - "sha256": "36fc04827b6cab409a668607126e95037c7a2581f6a10f25b2e340236c5a5ba5" - } - ] -} diff --git a/.dryforge/002/execution-support/reviews/unity-conformance-T5.json b/.dryforge/002/execution-support/reviews/unity-conformance-T5.json deleted file mode 100644 index cdd29e3..0000000 --- a/.dryforge/002/execution-support/reviews/unity-conformance-T5.json +++ /dev/null @@ -1,1704 +0,0 @@ -{ - "task": "T5", - "review": "독립 명세 적합성 검토", - "verdict": "specpass", - "status": "PASS_WITH_ADVISORIES", - "reviewedUtc": "2026-10-08T15:22:44.241960+00:00", - "sourceCommit": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "baselineCommit": "2d4b4a88e3c72a501fc84f1c9f7933c73452160d", - "headBefore": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "headAfter": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "sourceDirtyAfter": false, - "blockingFindings": [], - "summary": "지정 T5 범위 통과. 전체 Final EditMode103/103 및 PlayMode52/52 원문 XML·종료0·소스 전후 clean을 독립 확인했다. 최종 Android 검증은 T6이며 모든 카메라 각도의 시각 품질 합격을 주장하지 않는다.", - "checks": [ - { - "requirements": [ - "R04", - "R05", - "R08" - ], - "result": "pass", - "evidence": "9파일 전체 rawdiff 검토. 기존 decorations count7 대신 각 풍경 실제25개 구성을 다중집합으로 검사하고 원경4방향 회전을 확인한다. 아치는 실제 승인 프리팹 MeshCollider가 빈틈ray를 통과시키고 측면ray를 막는 양성·음성 대조를 함께 검사한다. 기존 목표·ID·도움·저장 판정은 유지된다." - }, - { - "requirements": [ - "R01", - "R06", - "R07" - ], - "result": "pass", - "evidence": "Editor API 경로로 기존 Sun Soft/.55와 MobileURP soft-shadow support·품질low·거리30을 적용한다. 승인25개SHA 및 보호692개 Git blob이 baseline과 일치하며 모델·재질RGB·메타/GUID·Stage비시각필드·Core·음성·글꼴 보존. 세 풍경 그림자on/off 동일화면 ROI를 별도 계산한 결과7042/7672/11749px로 원문 기록과 일치." - }, - { - "requirements": [ - "R03", - "R04", - "R07" - ], - "result": "pass", - "evidence": "Actual Main 테스트가 세풍경2+3합체, 떨어진양끝2개분리, 미리보기/취소/재입장, 빈보드/재공급 및 10개한묶음/10개개별구를 검사한다. ID·소속·숫자·대표위치·장비수·얼굴수·리본을 실제인스턴스에 대조한다. fixture주입과 실제세션호출을 구분하고 정상진행 증거로 확대하지 않는다." - }, - { - "requirements": [ - "R05", - "R07", - "R08" - ], - "result": "pass with scope limit", - "evidence": "보존된 strict 검사55개고유ID가 실제Collider 선택 후 실제Input Begin/Move/End로 끌리고 수집된다. 원문55행 모두 음수pitch를 사용하므로 허용카메라 발견통과로만 판정하며 55개모두지상시점이라고 기록하지 않는다. opaque장식 숫자95% 및 몸체/얼굴 실제깊이가림0 검사가 유지돼 통과. 실제Garden2/3숫자ROI59/59와31/31도 확인." - }, - { - "requirements": [ - "Android readiness compatibility" - ], - "result": "pass for pixel predicate", - "evidence": "두PS1 변경은 하늘RGB bounded135..163/197..225/210..239 한줄뿐이다. PID·유효save·가로화면·금색버튼·제목글자·4버튼표본·30회한도 guard가 그대로다. FinalCapture2 홈PNG를 별도 계산해sky531/gold18/title613/button4로 pixelReady=true; 실제AVD PID/save/시작검사는T6." - }, - { - "requirements": [ - "검증 출처" - ], - "result": "pass", - "evidence": "BaselineFull red Edit101/102(count7 vs25)·Play48/49(옛BlockArch조회)와 lighting-red(None vsSoft) 실제실패 확인. 최종155개평가 모두pass/skip0/inconclusive0 및raw로그exit0. FinalCapture2의101PNG+101JSON 전체메타는 동일b4d커밋/clean/묶음수=장비수;12개대표PNG 직접시각확인." - } - ], - "tests": [ - { - "phase": "editmode", - "total": 103, - "passed": 103, - "failed": 0, - "skipped": 0, - "inconclusive": 0, - "exitCode": 0, - "sourceCommitBefore": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "sourceCommitAfter": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "sourceDirtyBefore": false, - "sourceDirtyAfter": false, - "startedUtc": "2026-10-08T15:08:35.0047111Z", - "finishedUtc": "2026-10-08T15:08:50.6375751Z", - "caseResults": [ - { - "name": "MagnetismHeroes.Tests.CompositionAssetTests.AppCatalogControlsAndDraftGuidesAreConnectedWithoutUnlockBypass", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionAssetTests.IdenticalRowsCannotHideEachOthersUnitSpheresAfterContactPlacement", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionAssetTests.InspectorStageMatchesRepresentativeRule(6,System.Int32[],System.Int32[])", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionAssetTests.InspectorStageMatchesRepresentativeRule(10,System.Int32[],System.Int32[])", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionAssetTests.InspectorStageMatchesRepresentativeRule(14,System.Int32[],System.Int32[])", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionSessionTests.DistantSelectionProducesExactlyTwoFriendsAndFinalCompositionIgnoresHistory", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionSessionTests.EmptyAndEntireSplitKeepStateAndSave(0)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionSessionTests.EmptyAndEntireSplitKeepStateAndSave(3)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionSessionTests.InvalidCoordinatesNeverPartiallySplitOrMerge", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionSessionTests.MoveAndMergeCompleteWithOneWriteAndPreserveEveryIdentity", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionSessionTests.SplitAndMergeKeepCollectedAndCountedIdsThroughPuzzleRestore", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionSessionTests.UnknownMergeTargetDoesNotMoveOrWrite", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentAssetTests.EveryFixedStageHasExactlyOneAsset", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentAssetTests.FixedOrderMatchesEveryR07InitialTargetAndKind", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentSessionTests.AllIntegerPartitionsHaveLiveHintsThatReachEachCompositionTarget", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentSessionTests.CountingHelpRejoinsCollectedSplitBeforeFindingOtherFriendsThenCountsOnlyUncountedId", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentSessionTests.FreshSaveSolvesAllThirtyThroughActualCoreCallsUnlocksNextAndNeverThirtyOne", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentSessionTests.NextPrefersSavedPuzzleAndHelpRecoversFromAlternativeDisconnectedPartitions", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentVoiceTests.EveryLiveHelpPairAndBothActualGhostScriptsHaveOfflinePcmClips", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentVoiceTests.UserApprovedVoiceBytesRemainExact(\"count_01\",\"13A882CE1620140D447635E14F2D4758045D5E348960C17386DF1C720050C100\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentVoiceTests.UserApprovedVoiceBytesRemainExact(\"guide_collect\",\"62E15C9E38C36F4094A6C7E9C5148510141290B5F41DB5AAD9DC1D8F1A158594\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentVoiceTests.UserApprovedVoiceBytesRemainExact(\"guide_count\",\"DEBE29B89F3E2856F2BF4C5E01707A5F0A0657E5768C03BE385EC88FA2328B40\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentVoiceTests.UserApprovedVoiceBytesRemainExact(\"count_08\",\"FA8034948F6AC3E289E8E7E9FB447943760D4ED5AC577C8AB0C961C608FE49C2\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.AGroupMoveThatOverflowsALaterBallCannotChangeEarlierBallsOrCollectThem", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CollectedAndCountedIdsSurviveSplitMergeMoveAndRestore", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(1)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(2)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(3)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(4)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(5)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(6)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(7)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(8)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(9)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingEveryValueRequiresEveryCollectedBallToBeCountedOnce(10)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.CountingOneRequiresDragBeforeFirstCountAndDuplicateTapIsRejected", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.DefinitionsInputsAndReturnedSnapshotsCannotMutateOwnedBoard", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.DuplicateSpawnAndInvalidCoordinatesAreRejectedAtomically", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(1)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(2)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(3)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(4)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(5)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(6)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(7)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(8)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(9)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.EveryProperSubsetProducesExactlyTwoGroupsAndPreservesAllIds(10)", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.GoalUsesAllGroupsAsMultisetAndIgnoresShape", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.InvalidDefinitionsAreRejected", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.InvalidSplitsNeverMutateBoard", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.MergePreservesIdsAndRejectsSelfOrUnknownGroups", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.BoardRulesTests.SupplyIsLockedInBothPuzzleKindsAndFreePlayAllowsZeroThroughTen", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.SaveValidatorTests.CompletedStageIdsCannotDecreaseOnUpdateButReplayPuzzleIsAllowed", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.SaveValidatorTests.CompositionCannotHaveCollectionOrCountingRecords", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.SaveValidatorTests.FreePlayCannotRestoreElevenBallsAndRestoreRejectsInvalidInput", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.SaveValidatorTests.FreePlaySourceMustBeCompletedAndEmptyBoardCannotContainGroups", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.SaveValidatorTests.InvalidMembershipCoordinatesCameraAndCountingRecordsRejectWholeSave", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.SaveValidatorTests.SchemaMissingCollectionsInvalidStagesAndDuplicateRecordsAreRejected", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.SaveValidatorTests.ValidPuzzleAndEmptyFreePlayAreAccepted", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.StageLayoutTests.InputArraysAndReturnedTransformCopiesCannotChangeFixedLayout", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.StageLayoutTests.InvalidDecorationTransformsAndMissingEntriesAreRejected", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.StageLayoutTests.PuzzleAndFreePlaySourceIdsSelectOriginalFixedDecorationLayoutOnRestore", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.EditMode.Core.StageLayoutTests.SameBallPositionsWithDifferentDecorationsRemainDistinctAndDoNotChangeTheMath", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstSliceAssetTests.FirstPuzzleHasAnExecutableAppPrefab", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstSliceAssetTests.StageAssetConvertsToAnIndependentDefinition", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstSliceAssetTests.StaticKoreanFontContainsEveryVisibleFirstSliceCharacter", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.GameSessionTests.CompletedStageReplayKeepsCompletionAndItsPreviousFreePlaySeparately", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.GameSessionTests.FirstStageCannotCompleteWithoutACommittedCollection", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.GameSessionTests.IncompleteBoardRestoresCommittedPositionCameraAndCollection", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.GameSessionTests.LoadRecoveryWarningIsRetainedForDiagnostics", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.GameSessionTests.MoveAndCountCreateOneAtomicCompletionAndKeepTheSameBoard", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.BothDamagedOriginalsSurviveTwoSubsequentSuccessfulWrites", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.CommittedPositionAndCameraRoundTripWithoutPartialLoad", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.DuplicateJsonKeysAndTrailingContentAreRejected", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.InvalidGroupMembershipDoesNotReplaceAValidSave", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.LockedTemporaryFileReportsFailureAndKeepsTheLastValidSave", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingPrimaryAndBackupStartsFreshWithoutRecoveryError", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingPrimaryRestoresEntireValidBackup", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingPrimaryWithInvalidBackupPreservesEvidenceThroughTwoSaves", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingRequiredFieldsAreRejectedInsteadOfDefaulted(\"position\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingRequiredFieldsAreRejectedInsteadOfDefaulted(\"x\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingRequiredFieldsAreRejectedInsteadOfDefaulted(\"camera\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingRequiredFieldsAreRejectedInsteadOfDefaulted(\"countedBallIds\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.MissingRequiredFieldsAreRejectedInsteadOfDefaulted(\"latestFreePlay\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.SavedJsonContainsOnlyContractFields", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.ValidBackupRecoversWholeBoardAndDamagedOriginalSurvivesNextSave", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WithoutValidBackupLoaderDoesNotOverwriteDamagedEvidence", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WrongScalarTypesAreRejected(\"schemaVersion\",\"null\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WrongScalarTypesAreRejected(\"schemaVersion\",\"\\\"1\\\"\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WrongScalarTypesAreRejected(\"schemaVersion\",\"1.5\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WrongScalarTypesAreRejected(\"distance\",\"null\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WrongScalarTypesAreRejected(\"distance\",\"\\\"8\\\"\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WrongScalarTypesAreRejected(\"x\",\"null\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.LocalProgressStoreTests.WrongScalarTypesAreRejected(\"x\",\"\\\"0\\\"\")", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.PersistenceSessionTests.ActualFileAlternatesTwoUnfinishedBoardsAndLatestFreeWithoutLosingAnyField", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.PersistenceSessionTests.CompletionIsOneWriteReplacesOnlyLatestFreeAndReplayKeepsProgress", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.PersistenceSessionTests.TutorialRecordsAreUniqueValidatedAndPersistAcrossFreeAndPuzzleTransitions", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.PresentationAssetTests.AudioRoutesToExistingMixerAndRequiredNoticesAreStagedForBuild", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.PresentationAssetTests.FaceAtlasHasSixDistinctOfflineExpressionsAndDecorationsCannotReceiveInput", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.AllThirtyStagesConnectNaturalThemesWithOpenNearMeshCollidersAndColliderFreeDistantDecor", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.ApprovedBodyAndOneEquipmentAssemblyAreConnectedWithoutChangingSelectionSphere", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradeAssetTests.MobileDaylightSupportsSoftGroundShadowsWithoutReplacingThePlayableFloor", - "result": "Passed" - } - ] - }, - { - "phase": "playmode", - "total": 52, - "passed": 52, - "failed": 0, - "skipped": 0, - "inconclusive": 0, - "exitCode": 0, - "sourceCommitBefore": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "sourceCommitAfter": "b4d88546a2e9c44e9b6babcc54acaf69a01c75b4", - "sourceDirtyBefore": false, - "sourceDirtyAfter": false, - "startedUtc": "2026-10-08T15:10:09.1662246Z", - "finishedUtc": "2026-10-08T15:10:52.2870018Z", - "caseResults": [ - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.FacePreviewCancelAndNavigationFollowOnlyCommittedGroups", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.FourteenDisconnectedSelectionThenMergeIsAcceptedByFinalNumbers", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.MusicResumesCurrentThemeAfterPauseWithoutReplayingSfx", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.NativeCanceledSplitRestoresCommittedBoardAndClearsUnconfirmedSelection", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.NativePinchInSplitDragSameFrameCannotCommitSplitOrLeaveSelectionEffects", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.NativeShortSplitDragCompletesTenOnceAndDoesNotImmediatelyRemerge", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.OneThroughTenKeepExactSphereCountSizeAndCommittedLayoutWithOneDecorationSetPerGroup", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.SixContactPreviewAndConfirmedMergePreserveShapeAndCompleteOnce", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.SpokenNumberAndDuckingFollowSpeechQueueAndClearImmediatelyOnNavigation", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.CompositionPlayTests.ZeroAllAndOutsideSplitPreserveStateAndOrbitKeepsNumbersAboveAllSpheres", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.CollectingCurrentHelpTargetCancelsOldFindGuideWithoutLosingNumberQueue", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.CountingRefreshPreservesViewsAndClearsMarkersInSameFreeBoard", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.EveryCountingBallCanBeFoundPickedAndDirectlyDraggedAfterOrbit", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.EveryThemeSuppliesZeroThroughTenAndRestoresSameSceneryAfterSuccess", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.FirstSixAndTenGhostsPreserveRealBoardAndNeverRepeatOnReentry", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.MeshArchReallyHasAnOpenGapAndHelpHighlightsOnlyCurrentIdsWithoutExecuting", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.OneThroughTenLabelsKeepTheirMembershipAndPixelsThroughOpaqueDecorationAfterOrbit", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.SplitFiveNumbersRemainRenderedBehindTheActualGardenFrame", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.ContentPlayTests.SuccessfulCurrentHelpMergeCancelsCompositionGuideInSameFreeBoard", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.ActualBundledVoicePlaysThroughVoiceMixerAndClearsOnNavigation", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.ActualTouchPinchDoesNotStartANewDragUntilBothFingersAreLifted", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.BackgroundInteriorDropIsRejectedAndSurfaceContactRemainsPlayable", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.ButtonBlockedGestureAndPinchNeverCollectOrCountAFriend", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.CanceledAndOffscreenDragsRestoreOnlyCommittedPositions", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.CanceledDragCannotBeInheritedByANewFingerInTheSameFrame", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.CanceledPinchRestoresCommittedCameraAndIgnoresTheRemainingFinger", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.CountVoicesStayOrderedUnderFastInputAndNeverOverlapAGuide", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.FailedMoveCameraAndPauseSavesShowAVisibleNoticeAndRecoveryClearsIt", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.FlatFloorColliderMatchesTheVisibleMeshAndKeepsTheFriendReachable", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.FreeSupplyButtonsCanReachTenRejectMoreReturnAWholeFriendAndRespawnEmptyBoard", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.GroupNumberBelowViewStaysAboveAndVisible", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.GroupNumberFrontViewStaysAboveAndVisible", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.GroupNumberPreviewFollowsTheFriendWithoutChangingCommittedState", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.GroupNumberRolledViewStaysAboveAndVisible", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.InspectorButtonsNavigateAndHomeKeepsACameraAndListener", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.OrbitIsCommittedAtEndAndCanInspectEveryDirection", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.PinchReleaseOutsideRestoresCameraAndSuppressesRemainingTouch", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.RestartRestoresOnlyCommittedPuzzleStateAndDoesNotReplayCounts", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.SameUpdateCanceledAndOutsideDragsPreserveCommittedState", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.SameUpdateConcurrentShortPinchNeverCountsTheFriend", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.SameUpdateFastDragMovesWithoutCountingAnAlreadyCollectedFriend", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.SameUpdateSequentialShortTapsCompleteOnlyOnce", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.SameUpdateShortTapRespondsBeforeCollectionAndCountsOnceAfterCollection", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.SameUpdateTouchBeginningOnUiNeverCountsTheFriendAtRelease", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.SuccessfulTwoCountKeepsItsFinalNumbersDuringFreeSplitCancelAndCommit", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.TapBeforeCollectionIsRejectedThenDragAndTapCompleteOnTheSameBoard", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.FirstPuzzlePlayTests.TouchReleaseOutsideTheScreenCancelsAnOtherwiseValidDrag", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradePlayTests.ActualMainShowsThreeThemesAndEquipmentFollowsPreviewCancelMergeSplitAndReentry", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradePlayTests.AllWorldsProduceActualSoftShadowPixelsWithUnchangedBoardAndMaterials", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradePlayTests.BelowGroundComparisonKeepsOriginalPhysicalOcclusionAndActualBoardUnchanged", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradePlayTests.FiveBallMergeDisconnectedSplitAndZeroResupplyKeepActualIdsAcrossAllWorlds", - "result": "Passed" - }, - { - "name": "MagnetismHeroes.Tests.VisualUpgradePlayTests.TenSingleGroupsAndOneTenBallGroupRenderActualImportedResourceCounts", - "result": "Passed" - } - ] - } - ], - "captureMetadata": { - "png": 101, - "json": 101, - "viewportCounts": { - "1280x720": 16, - "2400x1080": 43, - "1600x1000": 42 - }, - "sourceMismatch": 0, - "equipmentGroupMismatch": 0, - "independentlyViewedPng": [ - "world1-soft-shadow.png", - "world11-soft-shadow.png", - "world21-soft-shadow.png", - "belowground-approved-clearing.png", - "belowground-original-island.png", - "world9-two-plus-three-phone-default.png", - "world11-merged-five-tablet-orbitzoom.png", - "world21-disconnected-split-two-three-phone-orbitzoom.png", - "world9-zero-tablet-default.png", - "world11-single-resupplied-phone-default.png", - "world21-ten-single-groups-tablet-orbitzoom.png", - "world1-ten-one-group-phone-default.png" - ] - }, - "resourceScope": { - "maxResidentActiveHierarchyTriangles": 237868, - "maxSharedMaterials": 28, - "maxRecordedEditorCameraRenderCpuWallMilliseconds": 1.7158000469207764, - "maxAllEditorLoadedTextureBytes": 185506865, - "interpretation": "GetComponentsInChildren() 기본호출은 비활성GameObject를 제외하며 active이나renderer-disabled인원래floor를포함한다. GPU제출삼각형·AndroidGPU/FPS·기기메모리합격으로해석하지않는다." - }, - "advisories": [ - { - "id": "A1-capture-method-text", - "severity": "advisory", - "observation": "캡처method문장의 including inactive retained scene objects는 실제API와맞지않는다. visual-review.json은active계층/renderer-disabled floor포함으로이미정정했다. root가T6최종소스확정전해당소스설명한줄을정정하기로했다. 표시·규칙변경이아니며T5재실행을요구하지않는다." - }, - { - "id": "A2-belowground-visual-limit", - "severity": "accepted limitation", - "observation": "동일board/camera/collider A/B에서새clearing의밑면이원래island보다훨씬넓게화면을가린다.6개중앙구ray는모두원래PlaygroundIsland가같은거리firstHit이고floorDepth