[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CBQWSJYSN3QEP43QW173NF":3,"skill-same:01M6CBQWSJYSN3QEP43QW173NF":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},"01M6CBQWSJYSN3QEP43QW173NF","03-ssot",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\u002F51-treasure-system\u002F03-ssot\u002FSKILL.md",[],[18],"03 ssot","2026-11-10 10:12:30",false,"# 03-ssot — 51-treasure-system\n\nStatus: ACTIVE\nAppliesTo: v10\n\n## SSOT Scope\n\n이 문서는 Treasure 시스템의 정본이다.\n\n- `ITEM_GRADE_TYPE` enum\n- `TREASURE_CHEST`, `TREASURE_REWARD` 테이블 스키마\n- `InventoryStorage` 내 Treasure 상태 모델 (`TreasureCurrent`, `TreasureCounts`)\n- `OpenCollectedChests` \u002F `OpenCurrentChest` 동작 규칙\n- TreasureManager \u002F RewardManager 경계\n\n`RewardData`와 `reward_group_id` 해석 정본은 [49-reward-system\u002F03-ssot](..\u002F..\u002F49-reward-system\u002F03-ssot\u002FSKILL.md)다.\n이 문서는 Treasure 전용 상태와 collect 규칙만 정의한다.\n\n---\n\n## A) Core Terms\n\n- `gradeType`: chest\u002Freward grade key (`ITEM_GRADE_TYPE`)\n- `reward_group_id`: Reward 시스템 지급 group key\n- `TreasureCurrent`: `InventoryTreasureCurrent` 하위 객체 (exp\u002Flevel 묶음, `InventoryStorage` 소속)\n- `exp`: 현재 treasure exp (`TreasureTreasureCurrent.Exp`)\n- `level`: 현재 treasure reward level (`TreasureTreasureCurrent.Level`)\n- `maxLevel`: `TREASURE_CHEST.level`의 최대값\n\n---\n\n## B) Enum Source\n\n`ITEM_GRADE_TYPE`의 입력 정본:\n\n- `input\u002FDomains\u002FGame\u002FENUM_ITEM.json`\n\n값:\n\n- `NONE`\n- `COMMON`\n- `RARE`\n- `EPIC`\n- `LEGENDARY`\n- `MYTHIC`\n\n규칙:\n\n- `UNCOMMON`은 사용하지 않는다.\n- chest count key는 `NONE`을 사용하지 않는다.\n\n---\n\n## C) Table Schema\n\n파일:\n\n- `input\u002FDomains\u002FGame\u002FTreasureTable.xlsx`\n\n### C-1) `TREASURE_CHEST`\n\n| field | type | note |\n|------|------|------|\n| `level` | int (pk) | chest reward level |\n| `treasure_grade_type` | `ITEM_GRADE_TYPE` | level reward grade metadata |\n| `max_exp` | int | collect 필요 exp |\n\n규칙:\n\n- `level`은 1-base 연속 값 사용을 권장한다.\n- `max_exp > 0`\n\n### C-2) `TREASURE_REWARD`\n\n| field | type | note |\n|------|------|------|\n| `index` | int (pk) | row key |\n| `treasure_grade_type` | `ITEM_GRADE_TYPE` (`group:true`) | grade별 reward fan-out group key |\n| `level` | int | group 내 level |\n| `condition_msg_id` | string | 조건 메시지 ID |\n| `condition_op` | `GAME_MESSAGE_OP_TYPE` | 조건 연산자 |\n| `condition_value` | `CBigInt` | 조건 목표값 |\n| `reward_group_id` | string | RewardManager apply key |\n\n규칙:\n\n- 같은 `treasure_grade_type` 아래 여러 row는 모두 적용 대상이다.\n- `reward_group_id`는 비어 있지 않아야 한다.\n\n### C-3) Container expectation\n\n코드젠 target:\n\n- `TB_TREASURE_CHEST.Get(level)`\n- `TB_TREASURE_CHEST.GetAll()`\n- `TB_TREASURE_REWARD.GetByGroup(ITEM_GRADE_TYPE)`\n\ncodegen 완료 상태. generated field name: `Treasure_grade_type` (PascalCase).\n\n---\n\n## D) Treasure State (in InventoryStorage)\n\nTreasure 상태는 `InventoryStorage` 내부에 포함된다. 별도 `TreasureStorage` 클래스는 없다.\n\n정본 모델:\n\n```csharp\npublic sealed class InventoryTreasureCurrent\n{\n    public int Exp { get; set; }\n    public int Level { get; set; } = 1;\n}\n\n\u002F\u002F InventoryStorage 내부:\n\u002F\u002F   InventoryTreasureCurrent TreasureCurrent { get; }\n\u002F\u002F   Dictionary\u003CITEM_GRADE_TYPE, int> TreasureCounts { get; }\n\u002F\u002F   GetTreasureCount \u002F AddTreasure \u002F SetTreasureCount \u002F AddTreasureExp \u002F ResetTreasure\n```\n\n기본값:\n\n- `TreasureTreasureCurrent.Exp = 0`\n- `TreasureTreasureCurrent.Level = 1`\n- 모든 treasure count 기본값은 0\n\n---\n\n## E) Condition Selection Rules (`selectBestRewardRow`)\n\n`TREASURE_REWARD.GetByGroup(gradeType)` 결과 중 조건에 부합하는 row 1개를 선택한다.\n\n1. `condition_msg_id`가 비어있으면 → 조건 통과 (조건 자체가 없음)\n2. `condition_msg_id`가 있고 `Condition_value`가 null이면 → **무조건 실패** (null 통과 절대 불가)\n3. `condition_msg_id`가 있고 `Condition_value`가 있으면 → `GameMessageManager.Instance.GetStat(condition_msg_id)` 값을 `GameMessageRule.IsConditionSatisfied(stat, condition_op, condition_value)` 로 비교\n4. 조건 통과한 row 중 `Level`이 가장 높은 row **1개**를 선택한다\n5. 조건 통과 row가 없으면 → 실패 (`TREASURE_REWARD_EMPTY`)\n\n---\n\n## F) `OpenCollectedChests` Rules\n\n입력:\n\n- `gradeType: ITEM_GRADE_TYPE`\n\n처리:\n\n1. `InventoryStorage.TreasureCounts[gradeType]`를 읽는다.\n2. count가 0 이하이면 valid no-op으로 종료한다.\n3. `TB_TREASURE_REWARD.GetByGroup(gradeType)`로 reward rows를 조회한다.\n4. `selectBestRewardRow`로 조건 충족 최고 레벨 row 1개를 선택한다.\n5. `count`회 반복하면서 best row의 `reward_group_id`를 `RewardManager.ApplyRewardGroup(...)`에 전달한다.\n6. 모든 지급이 성공하면 해당 grade count를 0으로 갱신한다.\n\n실패:\n\n- reward row 없음 \u002F 조건 충족 row 없음 \u002F 빈 `reward_group_id` \u002F `RewardManager` 실패 시 `CommonResult.Failure`\n- 실패 시 chest count는 변경되지 않는다.\n\n---\n\n## G) `OpenCurrentChest` Rules\n\n처리:\n\n1. `TreasureCurrent.Level`로 `TB_TREASURE_CHEST.Get(level)`을 조회한다.\n2. row가 없으면 실패한다.\n3. `TreasureCurrent.Exp \u003C row.max_exp`이면 valid no-op으로 종료한다.\n4. row의 `treasure_grade_type`로 `TB_TREASURE_REWARD.GetByGroup(...)`를 조회한다.\n5. `selectBestRewardRow`로 조건 충족 최고 레벨 row 1개를 선택한다.\n6. best row의 `reward_group_id`를 `RewardManager.ApplyRewardGroup(...)`에 전달한다.\n7. 성공하면 `TreasureCurrent.Exp -= row.max_exp`를 적용한다.\n8. `TreasureCurrent.Level++` 후 `TreasureCurrent.Level > maxLevel`이면 `1`로 wrap한다.\n\n규칙:\n\n- 한 번의 `OpenCurrentChest()` 호출은 현재 level 보상 1회만 처리한다.\n- 남은 exp가 다음 level `max_exp` 이상이어도 추가 collect는 다음 호출에서 수행한다.\n\n실패:\n\n- row 누락 \u002F 조건 충족 row 없음 \u002F 빈 `reward_group_id` \u002F `RewardManager` 실패 시 `CommonResult.Failure`\n- 실패 시 `TreasureCurrent.Exp`, `TreasureCurrent.Level`은 변경되지 않는다.\n\n---\n\n## H) TreasureReward Apply Semantics\n\n- `TREASURE_REWARD`는 `treasure_grade_type -> row[]` 테이블이다.\n- 각 row는 `condition_msg_id\u002Fcondition_op\u002Fcondition_value` 조건을 가진다.\n- 하나의 collect는 조건을 통과한 row 중 가장 높은 `Level` row 1개의 `reward_group_id`만 적용한다.\n- 개별 `reward_group_id` 내부의 랜덤 선택\u002F`REWARD` 해석은 RewardManager 정본을 따른다.\n\n---\n\n## I) Runtime Ownership\n\n- TreasureManager: table lookup + collect orchestration + InventoryStorage treasure mutation\n- InventoryStorage: treasure 상태 저장 (`TreasureCurrent`, `TreasureCounts`)\n- RewardManager: `reward_group_id` 지급 실행\n- InventoryManager: concrete inventory mutation (treasure 포함)\n\n---\n\n## J) Known Current-State Gaps\n\n현재 알려진 gap 없음.\n\n---\n\n## K) SaveData Integration\n\n- root JSON key: `\"treasure\"`\n- section codec: `SaveDataJsonCodecTreasure`\n- version gate: `TreasureVersion` (= 20, `CurrentVersion`과 동일)\n- serialize: `InventoryStorage` → treasure `JObject`\n- deserialize: treasure `JObject` → `InventoryStorage` treasure fields\n- current 상태는 `\"current\"` 하위 객체로 직렬화한다 (`exp`, `level`)\n- `TreasureCounts` dictionary key: enum name string (예: `\"COMMON\"`, `\"EPIC\"`)\n- `SavePayloadSummary`에 `TreasureChestTotal`, `TreasureLevel` 추가\n\n---\n\n## Related\n\n- [10-treasure-manager](..\u002F10-treasure-manager\u002FSKILL.md)\n- [49-reward-system\u002F03-ssot](..\u002F..\u002F49-reward-system\u002F03-ssot\u002FSKILL.md)\n",{},"main","8cb5bd877a3a7004867d2f55697cb9e4ff2f1734","skills\u002Fdevian-unity\u002F50-mobile-package\u002F51-treasure-system\u002F03-ssot","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 03-ssot",{"tool":36,"command":37},"bunx","bunx skills add devwinsoft\u002Fdevian --skill 03-ssot",{"tool":39,"command":40},"pnpm","pnpm dlx skills add devwinsoft\u002Fdevian --skill 03-ssot","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F50-mobile-package\u002F51-treasure-system\u002F03-ssot\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"]