[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CBRSFN1YN3CCYBKF51BATH":3,"skill-same:01M6CBRSFN1YN3CCYBKF51BATH":45},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":20,"linkOnly":21,"markdownContent":22,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":13,"repositoryHtmlUrl":27,"repositoryHomepageUrl":6,"ownerHtmlUrl":28,"ownerBlogUrl":6,"ownerDescription":29,"updatedAt":30,"install":31,"resources":44},"01M6CBRSFN1YN3CCYBKF51BATH","01-policy",null,"devwinsoft\u002Fdevian","devwinsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F22570160?v=4","01M6C6RR8W05NH8NPKQNCE3Q72","maoshy","01M6C6RRDJ5TJJCZXRTY801YDH",0,"MIT","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F50-mobile-package\u002F70-build-operation\u002F01-policy\u002FSKILL.md",[],[18,19],"policy","01 policy","2026-11-10 10:12:30",false,"# 70-build-operation — Policy\n\nStatus: ACTIVE\nAppliesTo: v1\n\n---\n\n## 1. Pipeline 빌드 테스트 정책\n\n### 1.1 Addressables NRE 진단 체크리스트\n\n`BuildPlayerContent`에서 `Object reference not set to an instance of an object` (duration: 0:00:00) 발생 시, 아래 순서로 진단한다.\n\n#### Step 1: Domain Reload 확인\n\n- **직전에 C# 파일을 수정했는가?** (스크립트 재컴파일 → domain reload)\n- Unity 6000.x 베타에서 domain reload 후 Addressables 내부 정적 캐시가 깨지는 버그가 존재한다.\n- **해결**: Unity 에디터 재시작 후 재시도.\n\n#### Step 2: Roslyn (script-execute) 빌드 테스트\n\ndomain reload 무관하게 실패하면, MCP `script-execute`로 동일 빌드를 실행한다:\n\n```csharp\n\u002F\u002F Roslyn 진단 빌드 — 이것이 성공하면 domain reload 문제\nusing UnityEditor.AddressableAssets;\nusing UnityEditor.AddressableAssets.Build;\nusing UnityEditor.AddressableAssets.Settings;\n\npublic class DiagBuild\n{\n    public static object Main()\n    {\n        AddressablesPlayerBuildResult result;\n        AddressableAssetSettings.BuildPlayerContent(out result);\n        if (result == null) return \"NULL\";\n        if (!string.IsNullOrEmpty(result.Error)) return $\"FAIL: {result.Error}\";\n        return $\"OK: {result.OutputPath}\";\n    }\n}\n```\n\n| Roslyn 결과 | 원인 | 조치 |\n|-------------|------|------|\n| 성공 | domain reload로 인한 Addressables 내부 상태 깨짐 | Unity 재시작 |\n| 실패 | Addressables 설정 자체 문제 | Step 3으로 |\n\n#### Step 3: Settings Diagnostic Dump\n\nRoslyn으로 Settings 상태를 덤프하여 null group, 잘못된 profile, 누락된 schema를 확인:\n\n- `AddressableAssetSettingsDefaultObject.Settings` null 여부\n- `activeProfileId` 유효성\n- `ActivePlayerDataBuilder` 설정 여부\n- 각 group의 schemas, entries에서 null 존재 여부\n- 각 `BundledAssetGroupSchema`의 `BuildPath`\u002F`LoadPath` 접근 시 예외 여부\n\n#### Step 4: 디스크 상태 확인\n\n- `Assets\u002FAddressableAssetsData\u002FAndroid\u002Faddressables_content_state.bin` 존재\u002F크기\n- `Library\u002Fcom.unity.addressables\u002F` 디렉토리 존재\n- `ServerData\u002F{BuildTarget}\u002F` 디렉토리 내용\n\n---\n\n### 1.2 금지 사항 (Hard Rule)\n\n빌드 전 캐시 조작으로 NRE를 유발한 이력이 있으므로, 아래 행위를 금지한다:\n\n| 금지 행위 | 이유 |\n|-----------|------|\n| `Library\u002Fcom.unity.addressables\u002F` 수동 삭제 | Addressables 메모리 캐시와 디스크 불일치 → NRE |\n| `ServerData\u002F{BuildTarget}\u002F` 빌드 전 삭제 | Remote group 빌드 시 내부 참조 실패 → NRE |\n| `addressables_content_state.bin` 빌드 전 삭제 | incremental build 상태 깨짐 가능 |\n| `AddressableAssetSettings.CleanPlayerContent()` 빌드 직전 호출 | 메모리-디스크 불일치 유발 |\n\n**허용되는 유일한 캐시 정리 방법**: Unity 에디터 재시작.\n\n---\n\n### 1.3 Pipeline 빌드 실행 컨텍스트\n\n`BuildAutomationWindow.RunBuild()`는 `async void`이며, OnGUI Button 클릭에서 동기적으로 시작된다.\n\n```\nOnGUI → Button Click → RunBuild() → BundleBuildRunner.Run() (동기)\n                                   → await BundleUploadRunner.Run() (비동기)\n                                   → AndroidBuildRunner \u002F IOSBuildRunner (동기)\n```\n\n- **OnGUI에서 빌드 직접 호출 금지**: `EditorApplication.delayCall`로 defer해야 한다.\n  - OnGUI 내에서 `BuildPipeline.BuildPlayer` 호출 시 GUI 레이아웃 에러 발생.\n  - `UnitySynchronizationContext` (player loop 내)에서 호출 시 \"A player build cannot be executed while inside the player loop\" 에러.\n- **`EditorApplication.delayCall` 패턴**: 모든 Build\u002FUpload 버튼 클릭은 `EditorApplication.delayCall += () => Run...()` 형태로 호출한다.\n- **`await Task.Run()` 금지**: context switch로 인해 이후 코드가 player loop 안에서 실행됨. `GitRunner`는 `process.WaitForExit()` (동기) + `Task.FromResult` 사용.\n- **`Task.Yield()` 금지**: player loop context에서 continuation이 실행되어 동일 문제 발생.\n- NRE 발생 시 실행 컨텍스트가 아닌 **domain reload \u002F Addressables 내부 상태**를 먼저 의심한다.\n\n---\n\n## 2. Remote Bundle 경로 정책 (SSOT: remoteCdnUrl)\n\n### 2.0 경로 매칭 규칙\n\nRemote 번들의 업로드 경로와 런타임 다운로드 경로는 **동일 규칙**으로 매칭되어야 한다.\n\n| 항목 | 경로 규칙 |\n|------|-----------|\n| Addressables Remote.LoadPath | `{remoteCdnUrl}\u002Fv{bundleVersion}\u002F[BuildTarget]` |\n| BundleUploadRunner dest | `{releaseRepoRoot}\u002Fv{bundleVersion}\u002F{BuildTarget}\u002F` |\n| git commit 대상 | `v{bundleVersion}\u002F{BuildTarget}` |\n\n- **SSOT**: `BuildAutomationSettings.remoteCdnUrl` 필드 하나로 CDN URL을 관리한다.\n- **자동 동기화**: BundleBuildRunner가 빌드 전에 Addressables Profile의 `Remote.LoadPath`를 Settings의 `remoteCdnUrl` 기반으로 자동 설정한다.\n- **버전 분리**: `v{bundleVersion}\u002F` prefix로 버전별 번들을 분리 저장한다. 이전 버전 번들은 삭제하지 않는다.\n\n---\n\n## 3. Bundle Upload 정책\n\n### 3.1 복사 대상\n\n- `Build & Load Paths`가 **Remote**인 group의 빌드 산출물만 복사한다.\n- Remote 여부 판별: `LoadPath` resolved 값이 `http:\u002F\u002F` 또는 `https:\u002F\u002F`로 시작.\n- 복사 원본: `ServerData\u002F{BuildTarget}\u002F` (Remote group의 BuildPath resolved 값)\n\n### 3.2 Stale 파일 처리\n\n- dest 디렉토리를 **clean 후 전체 복사**한다. 타임스탬프 기반 필터링 금지.\n- Addressables incremental build는 변경되지 않은 번들을 재생성하지 않으므로, 타임스탬프 필터는 유효한 파일을 누락시킨다.\n\n### 3.3 Git Commit\n\n- `nothing to commit` (exit code 1)은 **warning** 처리. 빌드 파이프라인을 중단하지 않는다.\n- 실제 git 에러만 **error** 처리.\n\n---\n\n## Related\n\n- Parent: [00-overview](..\u002F00-overview\u002FSKILL.md)\n- [50-mobile-package\u002F01-policy](..\u002F..\u002F01-policy\u002FSKILL.md)\n- [21-bundle-build](..\u002F21-bundle-build\u002FSKILL.md)\n- [22-bundle-upload](..\u002F22-bundle-upload\u002FSKILL.md)\n",{},"main","8cb5bd877a3a7004867d2f55697cb9e4ff2f1734","skills\u002Fdevian-unity\u002F50-mobile-package\u002F70-build-operation\u002F01-policy","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft","Kim Hyong Joon","2026-04-12 18:44:10",{"source":7,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add devwinsoft\u002Fdevian --skill 01-policy",{"tool":37,"command":38},"bunx","bunx skills add devwinsoft\u002Fdevian --skill 01-policy",{"tool":40,"command":41},"pnpm","pnpm dlx skills add devwinsoft\u002Fdevian --skill 01-policy","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F50-mobile-package\u002F70-build-operation\u002F01-policy\u002FSKILL.md",true,[],[46,52,60,68,77,84],{"skillNo":47,"name":48,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":49,"categories":50,"keywords":51,"publishedAt":20},"01M6CBX3K2JJV38ANHVGCAT8A0","devian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002FSKILL.md",[],[48],{"skillNo":53,"name":54,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":20},"01M6CBX210BFMM8AMN770J9WY8","90-project-archive","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F90-project-archive\u002FSKILL.md",[],[58,59],"archive","90 project archive",{"skillNo":61,"name":62,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":20},"01M6CBWZSGRR9R3YJZB3177667","30-env-management","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F30-env-management\u002FSKILL.md",[],[66,67],"management","30 env management",{"skillNo":69,"name":70,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":20},"01M6CBWYD3TGWBQV7V35551THF","19-page-push-send","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F19-page-push-send\u002FSKILL.md",[],[74,75,76],"push","page","19 page push send",{"skillNo":78,"name":79,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":82,"publishedAt":20},"01M6CBWWRQ05A7604GHJKTPJTF","18-page-savedata","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F18-page-savedata\u002FSKILL.md",[],[75,83],"18 page savedata",{"skillNo":85,"name":86,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":89,"publishedAt":20},"01M6CBWVH4S2VF1V7B2GBTESCT","17-page-deobfuscate","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F17-page-deobfuscate\u002FSKILL.md",[],[75,90],"17 page deobfuscate"]