[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CBCWK2QBCRMC4B2NX45YXP":3,"skill-same:01M6CBCWK2QBCRMC4B2NX45YXP":44},{"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":19,"linkOnly":20,"markdownContent":21,"markdownBody":21,"frontMatter":22,"refName":23,"commitSha":24,"skillDirectory":25,"forksCount":13,"repositoryHtmlUrl":26,"repositoryHomepageUrl":6,"ownerHtmlUrl":27,"ownerBlogUrl":6,"ownerDescription":28,"updatedAt":29,"install":30,"resources":43},"01M6CBCWK2QBCRMC4B2NX45YXP","16-sound-tables",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\u002F22-sound-package\u002F16-sound-tables\u002FSKILL.md",[],[18],"16 sound tables","2026-11-10 10:12:30",false,"# 16-sound-tables\n\nStatus: ACTIVE\nAppliesTo: v10\nType: Data\n\n## Purpose\n\nTB_SOUND \u002F TB_VOICE 테이블의 책임 분리와 컬럼 규약을 고정한다.\n\n---\n\n## Domain Ownership (Important)\n\n### 테이블 소유 도메인\n\n- **최종적으로 TB_SOUND, TB_VOICE는 Sound 도메인 소유**이다.\n- 네임스페이스 목표: `Devian.Domain.Sound`\n\n### 현재 상태 (Sound 도메인 완료)\n\n| 항목 | 상태 |\n|------|------|\n| 데이터 파일 | `input\u002FDomains\u002FSound\u002FSoundTable.xlsx` |\n| Generated 위치 | `com.devian.foundation\u002FSamples~\u002FSoundPackage` |\n| 네임스페이스 | `Devian.Domain.Sound` |\n\n> 도메인 설계는 `19-sound-domain\u002FSKILL.md` 참조.\n\n---\n\n## Hard Rules\n\n### partial class 패턴 (Generated + 확장)\n\n- TB_SOUND, TB_VOICE 컨테이너는 **Generated 코드에서 `partial class`로 선언**된다.\n- 수기 유지 파일(TB_SOUND.cs, TB_VOICE.cs)은 **반드시 `partial` 키워드를 사용**하여 확장한다.\n- Generated 클래스(SOUND, VOICE)는 PascalCase 프로퍼티를 사용하며, ISoundRow\u002FIVoiceRow 인터페이스는 snake_case를 사용한다.\n- **Adapter 패턴**으로 Generated 클래스를 인터페이스에 맞춘다: `SoundRowAdapter`, `VoiceRowAdapter`\n\n### TB_SOUND\n\n- TB_SOUND는 **\"재생 단위(실제 AudioClip)\"의 정본**이며, `key_bundle` 컬럼이 반드시 존재한다.\n- **`row_id`가 PK**(내부 row 식별자, 재생 단위)이며, `sound_id`는 **논리 그룹 키**(중복 허용)이다.\n- 동일 `sound_id`에 여러 row가 존재할 수 있다 (weight 기반 랜덤 선택 지원).\n- 모든 사운드 재생은 `sound_id`를 통해 TB_SOUND를 참조하며, 해당 sound_id의 후보 row 중 weight 기반으로 1개를 선택한다.\n- `weight` 값이 0 이하인 경우 1로 취급한다.\n\n### TB_VOICE (SOUND 미참조, 독립 운용)\n\n- TB_VOICE는 **\"voice_id 중심의 논리 정의 + 모든 지원 언어 clip 매핑\"**을 단일 테이블에 가진다.\n- **TB_VOICE의 언어별 clip_* 값은 AudioClip 에셋 경로\u002F키(clipPath)를 직접 사용한다.**\n  - VOICE는 SOUND 테이블을 참조하지 않는다 (sound_id 매핑\u002F연동 금지)\n  - clip_* 값은 더 이상 sound_id가 아니라 **clipPath(AudioClip 에셋 경로\u002F키)**이다.\n  - clip 경로는 IVoiceRow.TryGetClipColumn()으로 직접 조회\n- **TB_VOICE는 언어별로 테이블을 쪼개지 않는다.**\n  - 예: `VOICE_ko`, `VOICE_en` 같은 키\u002F테이블 분리 금지\n\n### 언어 컬럼 명명 규칙\n\n- 언어별 컬럼명은 Unity `SystemLanguage.ToString()` 결과와 동일한 문자열을 사용한다.\n- 예: `SystemLanguage.Korean.ToString()` == `\"Korean\"` → `clip_Korean`\n\n---\n\n## Tables\n\n### TB_SOUND 컬럼\n\n**필수 컬럼:**\n\n| 컬럼명 | 타입 | 설명 |\n|--------|------|------|\n| `row_id` | int | **PK**, 내부 row 식별자 (재생 단위) |\n| `sound_id` | string | 논리 사운드 식별자 (그룹 키, 중복 허용) |\n| `isBundle` | bool | 로딩 소스 (true=Bundle, false=Resources.Load) |\n| `key_bundle` | string | **로드\u002F언로드 단위 키** (Bundle label 또는 Resource 그룹 키) |\n| `path` | string | 에셋 경로 |\n| `channel` | SoundChannelType | 재생 채널 (enum: Bgm, Effect, Ui, Voice) |\n| `loop` | bool | 루프 여부 |\n| `cooltime` | float | 재생 쿨타임 (초) |\n| `is3d` | bool | 3D 사운드 여부 |\n| `distance_near` | float | 3D near 거리 (minDistance), 기본값 1.0 |\n| `distance_far` | float | 3D far 거리 (maxDistance), 기본값 500.0 |\n| `weight` | int | 랜덤 선택 가중치, 기본값 1 |\n| `volume_scale` | float | 볼륨 스케일, 기본값 1.0 |\n| `pitch_min` | float | 피치 랜덤 최소, 기본값 1.0 |\n| `pitch_max` | float | 피치 랜덤 최대, 기본값 1.0 |\n\n**key_bundle 규약 (Hard Rule):**\n- **로드\u002F언로드는 `key_bundle` 단위로만 수행**한다 (key_group 제거됨).\n- `isBundle=true`: Addressables label\u002Fkey로 사용\n- `isBundle=false`: Resources 폴더 그룹 키로 사용 (AssetManager Resource API 기반 로드\u002F언로드)\n- isBundle=false인 행도 반드시 key_bundle을 채워야 한다.\n\n**컬럼명 변경 히스토리:**\n- `key` → ~~`key_group`~~ → **삭제** (key_bundle로 통합)\n- `source` → `isBundle` (enum → bool 단순화)\n- `bundle_key` → `key_bundle` (접두어 통일)\n- `channel` → SoundChannelType enum (string → enum)\n\n**weight 규약 (SOUND 전용):**\n- `weight`는 **SOUND 테이블 전용** 필드이다.\n- 동일 sound_id 후보 rows 중 가중치 기반 랜덤 선택에 사용된다.\n- VOICE 테이블에는 weight 필드가 없으며 사용하지 않는다.\n\n> **3D 파라미터**: `distance_near`\u002F`distance_far`는 BaseAudioManager의 Play3D에서 직접 사용된다.\n> 자세한 내용은 `20-base-audio-manager\u002FSKILL.md` 참조.\n\n### TB_VOICE 컬럼\n\n**필수 컬럼:**\n\n| 컬럼명 | 타입 | 설명 |\n|--------|------|------|\n| `voice_id` | string | PK, 보이스 식별자 |\n| `key_bundle` | string | **로드\u002F언로드 단위 키** (Voice 전용 번들) |\n| `cooltime` | float | 재생 쿨타임 |\n| `is3d` | bool | 3D 여부 |\n| `distance_near` | float | 3D near 거리 (minDistance), 기본값 1.0 |\n| `distance_far` | float | 3D far 거리 (maxDistance), 기본값 500.0 |\n| `volume_scale` | float | 볼륨 스케일, 기본값 1.0 |\n| `pitch_min` | float | 피치 랜덤 최소, 기본값 1.0 |\n| `pitch_max` | float | 피치 랜덤 최대, 기본값 1.0 |\n\n**제거된 컬럼:**\n- `speaker`, `category`, `priority` — 삭제됨 (게임 로직 메타이므로 오디오 테이블 범위 밖)\n- `key_group` — 삭제됨 (key_bundle로 통합)\n\n**언어별 컬럼:**\n\n| 컬럼명 패턴 | 타입 | 설명 |\n|-------------|------|------|\n| `clip_Korean` | string | 한국어 clip 파일 경로 |\n| `clip_English` | string | 영어 clip 파일 경로 |\n| `clip_Japanese` | string | 일본어 clip 파일 경로 |\n| `clip_Chinese` | string | 중국어 clip 파일 경로 (간체\u002F번체 통합) |\n| `clip_{SystemLanguageName}` | string | 기타 언어 clip 파일 경로 |\n\n> **중국어 통합**: `SystemLanguage.Chinese`를 사용하며, 간체\u002F번체 구분 없이 `clip_Chinese` 단일 컬럼으로 관리한다.\n\n> **clip_* 값의 의미**: clip_* 컬럼 값은 **AudioClip 에셋 경로\u002F키(clipPath)**이다.\n> 더 이상 sound_id가 아니며, VOICE는 SOUND 테이블을 참조하지 않는다.\n\n**VOICE 상수 (코드 하드코딩):**\n- `isBundle = true` (항상 Bundle)\n- `channel = SoundChannelType.Voice` (항상 Voice)\n- `loop = false` (항상 비루프)\n- `volume_scale = 1f`\n- `pitch_min = 1f`, `pitch_max = 1f`\n\n---\n\n## Row4 주석 규약 (Excel 시트)\n\nExcel 테이블의 Row4(주석 행)에 아래 정보를 반드시 기입한다:\n\n### SOUND 시트 Row4\n\n| 컬럼 | Row4 주석 (필수) |\n|------|------------------|\n| `channel` | `enum:SoundChannelType: Bgm=0, Effect=1, Ui=2, Voice=3, Max=4` |\n| `isBundle` | `false=Resources.Load, true=Bundle` |\n\n### VOICE 시트 Row4\n\nVOICE 시트에는 기본값 규약을 Row4 주석으로 기입한다:\n\n| 컬럼 (아무 컬럼) | Row4 주석 (필수) |\n|------------------|------------------|\n| `voice_id` (또는 첫 컬럼) | `VOICE defaults: isBundle=true, channel=Voice, loop=false` |\n\n> Row4 주석은 코드 생성기\u002F문서화 도구에서 참조할 수 있다.\n\n---\n\n## IAudioRowBase Interface\n\nSOUND와 VOICE가 공통으로 구현하는 인터페이스:\n\n```csharp\npublic interface IAudioRowBase\n{\n    string key_bundle { get; }\n    bool isBundle { get; }\n    SoundChannelType channel { get; }\n    bool loop { get; }\n    float cooltime { get; }\n    bool is3d { get; }\n    float distance_near { get; }\n    float distance_far { get; }\n    float volume_scale { get; }\n    float pitch_min { get; }\n    float pitch_max { get; }\n}\n```\n\n> **key_group 제거**: 로드\u002F언로드는 `key_bundle` 단위로만 수행한다.\n\n- `ISoundRow : IAudioRowBase` — SOUND 전용 멤버 추가 (sound_id, row_id, path, weight)\n- `IVoiceRow : IAudioRowBase` — VOICE 전용 멤버 추가 (voice_id, TryGetClipColumn)\n\n---\n\n## Validation\n\n### TB_VOICE clip 경로 검증\n\n- TB_VOICE에 존재하는 `clip_*` 값은 유효한 파일 경로여야 한다.\n- 없으면 로드 단계에서 **경고 로그 + 스킵** (기본 정책)\n- 프로젝트 설정에 따라 **빌드 실패** 정책으로 전환 가능\n\n---\n\n## See Also\n\n- `skills\u002Fdevian-unity\u002F22-sound-package\u002F19-sound-domain\u002FSKILL.md` — **Sound 도메인 설계 (SSOT)**\n- `skills\u002Fdevian-unity\u002F22-sound-package\u002F17-sound-manager\u002FSKILL.md` — SoundManager 규약\n- `skills\u002Fdevian-unity\u002F22-sound-package\u002F18-voice-table-resolve\u002FSKILL.md` — Voice Resolve 규약\n- `skills\u002Fdevian-unity\u002F22-sound-package\u002F20-base-audio-manager\u002FSKILL.md` — BaseAudioManager 공통 Play 규약\n",{},"main","8cb5bd877a3a7004867d2f55697cb9e4ff2f1734","skills\u002Fdevian-unity\u002F22-sound-package\u002F16-sound-tables","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":31,"prompt":41,"zipAvailable":42},[32,35,38],{"tool":33,"command":34},"npx","npx skills add devwinsoft\u002Fdevian --skill 16-sound-tables",{"tool":36,"command":37},"bunx","bunx skills add devwinsoft\u002Fdevian --skill 16-sound-tables",{"tool":39,"command":40},"pnpm","pnpm dlx skills add devwinsoft\u002Fdevian --skill 16-sound-tables","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F22-sound-package\u002F16-sound-tables\u002FSKILL.md",true,[],[45,51,59,67,76,83],{"skillNo":46,"name":47,"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":48,"categories":49,"keywords":50,"publishedAt":19},"01M6CBX3K2JJV38ANHVGCAT8A0","devian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002FSKILL.md",[],[47],{"skillNo":52,"name":53,"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":54,"categories":55,"keywords":56,"publishedAt":19},"01M6CBX210BFMM8AMN770J9WY8","90-project-archive","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F90-project-archive\u002FSKILL.md",[],[57,58],"archive","90 project archive",{"skillNo":60,"name":61,"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":62,"categories":63,"keywords":64,"publishedAt":19},"01M6CBWZSGRR9R3YJZB3177667","30-env-management","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F30-env-management\u002FSKILL.md",[],[65,66],"management","30 env management",{"skillNo":68,"name":69,"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":70,"categories":71,"keywords":72,"publishedAt":19},"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",[],[73,74,75],"push","page","19 page push send",{"skillNo":77,"name":78,"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":79,"categories":80,"keywords":81,"publishedAt":19},"01M6CBWWRQ05A7604GHJKTPJTF","18-page-savedata","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F18-page-savedata\u002FSKILL.md",[],[74,82],"18 page savedata",{"skillNo":84,"name":85,"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":86,"categories":87,"keywords":88,"publishedAt":19},"01M6CBWVH4S2VF1V7B2GBTESCT","17-page-deobfuscate","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F17-page-deobfuscate\u002FSKILL.md",[],[74,89],"17 page deobfuscate"]