@aqian0/dsh-unity 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/LICENSE-UNITY-COMPANION.md +122 -0
  3. package/LICENSE.md +31 -0
  4. package/NOTICE.md +90 -0
  5. package/README.md +273 -2
  6. package/cordis.patch.yml +22 -0
  7. package/docs/design.md +297 -0
  8. package/icon.svg +16 -0
  9. package/lib/frontmatter.js +392 -0
  10. package/lib/gating.js +105 -0
  11. package/lib/harness.js +16 -0
  12. package/lib/index.js +171 -0
  13. package/lib/project.js +210 -0
  14. package/lib/registry.js +268 -0
  15. package/lib/requirements.js +214 -0
  16. package/lib/skills.js +191 -0
  17. package/lib/types/frontmatter.d.ts +84 -0
  18. package/lib/types/gating.d.ts +80 -0
  19. package/lib/types/harness.d.ts +152 -0
  20. package/lib/types/index.d.ts +75 -0
  21. package/lib/types/project.d.ts +93 -0
  22. package/lib/types/registry.d.ts +96 -0
  23. package/lib/types/requirements.d.ts +67 -0
  24. package/lib/types/skills.d.ts +54 -0
  25. package/lib/types/watch.d.ts +34 -0
  26. package/lib/watch.js +84 -0
  27. package/locale/en.json +6 -0
  28. package/locale/zh.json +6 -0
  29. package/package.json +70 -4
  30. package/skills/2d-pixel-perfect/SKILL.md +208 -0
  31. package/skills/2d-pixel-perfect/references/api-reference.md +81 -0
  32. package/skills/2d-pixel-perfect/references/camera-setup-builtin.cs +40 -0
  33. package/skills/2d-pixel-perfect/references/camera-setup-urp.cs +49 -0
  34. package/skills/2d-pixel-perfect/references/pipeline-detection.cs +50 -0
  35. package/skills/2d-pixel-perfect/references/sprite-settings.cs +37 -0
  36. package/skills/asset-transformer-toolkit/SECURITY.md +15 -0
  37. package/skills/asset-transformer-toolkit/SKILL.md +39 -0
  38. package/skills/asset-transformer-toolkit/references/create-importer.md +121 -0
  39. package/skills/asset-transformer-toolkit/references/lods.md +61 -0
  40. package/skills/asset-transformer-toolkit/references/rule-api.md +154 -0
  41. package/skills/asset-transformer-toolkit/references/ruleblock-api.md +37 -0
  42. package/skills/asset-transformer-toolkit/references/ruleset-api.md +138 -0
  43. package/skills/asset-transformer-toolkit/references/rulesets-and-actions.md +227 -0
  44. package/skills/audio-setup-mixers/SECURITY.md +16 -0
  45. package/skills/audio-setup-mixers/SKILL.md +127 -0
  46. package/skills/audio-setup-mixers/references/api.md +162 -0
  47. package/skills/build-gtk/SECURITY.md +16 -0
  48. package/skills/build-gtk/SKILL.md +200 -0
  49. package/skills/build-gtk/references/api-reference.md +33 -0
  50. package/skills/build-gtk/references/graph-api-gaps.md +96 -0
  51. package/skills/build-gtk/references/runtime-and-visualization.md +191 -0
  52. package/skills/build-gtk/references/state-machine-api.md +270 -0
  53. package/skills/build-gtk/references/where-the-docs-stop.md +49 -0
  54. package/skills/build-live-game/SKILL.md +159 -0
  55. package/skills/build-live-game/references/achievements.md +757 -0
  56. package/skills/build-live-game/references/apis.md +271 -0
  57. package/skills/build-live-game/references/authentication.md +431 -0
  58. package/skills/build-live-game/references/battlepass.md +904 -0
  59. package/skills/build-live-game/references/blueprints-and-samples.md +75 -0
  60. package/skills/build-live-game/references/cloud-code-cloudbehaviours.md +1111 -0
  61. package/skills/build-live-game/references/cloud-code-modules.md +1276 -0
  62. package/skills/build-live-game/references/cloud-code.md +58 -0
  63. package/skills/build-live-game/references/cloud-save.md +464 -0
  64. package/skills/build-live-game/references/deployment.md +231 -0
  65. package/skills/build-live-game/references/orientation.md +30 -0
  66. package/skills/build-live-game/references/player-account.md +821 -0
  67. package/skills/build-live-game/references/remote-config.md +96 -0
  68. package/skills/build-live-game/references/services-overview.md +76 -0
  69. package/skills/build-live-game/references/tooling.md +375 -0
  70. package/skills/build-live-game/references/ugs-cli.md +86 -0
  71. package/skills/craft-game-floors/SECURITY.md +23 -0
  72. package/skills/craft-game-floors/SKILL.md +134 -0
  73. package/skills/craft-game-floors/references/art-direction.md +85 -0
  74. package/skills/craft-game-floors/references/camera-rigs.md +51 -0
  75. package/skills/craft-game-floors/references/character-motion.md +35 -0
  76. package/skills/craft-game-floors/references/game-audio.md +47 -0
  77. package/skills/craft-game-floors/references/game-feel.md +68 -0
  78. package/skills/craft-game-floors/references/game-shell.md +45 -0
  79. package/skills/craft-game-floors/references/live-floor.md +35 -0
  80. package/skills/craft-game-floors/references/placeholder-assets.md +35 -0
  81. package/skills/craft-game-floors/references/scene-lighting.md +52 -0
  82. package/skills/craft-game-floors/references/toon-shading.md +34 -0
  83. package/skills/craft-game-floors/references/vfx.md +40 -0
  84. package/skills/craft-game-floors/references/visual-scorecard.md +43 -0
  85. package/skills/create-2d-physics/SECURITY.md +16 -0
  86. package/skills/create-2d-physics/SKILL.md +128 -0
  87. package/skills/create-2d-physics/references/2d-physics-core.md +155 -0
  88. package/skills/create-2d-physics/references/legacy-2d-physics.md +45 -0
  89. package/skills/create-tile-palette-or-rule-tiles/SKILL.md +95 -0
  90. package/skills/create-tile-palette-or-rule-tiles/references/hexagonalruletile.md +71 -0
  91. package/skills/create-tile-palette-or-rule-tiles/references/palette-create.md +38 -0
  92. package/skills/create-tile-palette-or-rule-tiles/references/ruletile-createempty.md +37 -0
  93. package/skills/create-tile-palette-or-rule-tiles/references/ruletile-createfromsegment.md +219 -0
  94. package/skills/create-tile-palette-or-rule-tiles/references/ruletile.md +95 -0
  95. package/skills/create-tile-palette-or-rule-tiles/scripts/CreatePaletteTemplate.cs +148 -0
  96. package/skills/create-tile-palette-or-rule-tiles/scripts/ManualWorkflowExample.cs +63 -0
  97. package/skills/create-tile-palette-or-rule-tiles/scripts/RuleTileGenerator.cs +61 -0
  98. package/skills/create-tile-palette-or-rule-tiles/scripts/TilemapRuleTileCreateFromSegment.cs +225 -0
  99. package/skills/fix-3d-physics-collisions/SECURITY.md +17 -0
  100. package/skills/fix-3d-physics-collisions/SKILL.md +139 -0
  101. package/skills/fix-3d-physics-collisions/references/collision-checklist.md +80 -0
  102. package/skills/fix-3d-physics-collisions/references/collision-debugger.md +18 -0
  103. package/skills/fix-3d-physics-collisions/references/console-errors-table.md +9 -0
  104. package/skills/fix-3d-physics-collisions/references/kinematic-trigger-facts.md +18 -0
  105. package/skills/fix-3d-physics-collisions/references/other-symptoms.md +29 -0
  106. package/skills/fix-3d-physics-collisions/references/raycast-checklist.md +8 -0
  107. package/skills/fix-3d-physics-collisions/references/topic-map.md +42 -0
  108. package/skills/fix-3d-physics-collisions/references/trigger-checklist.md +9 -0
  109. package/skills/fix-3d-physics-collisions/scripts/CollisionDebugger.cs +561 -0
  110. package/skills/generate-editor-search-query/SECURITY.md +17 -0
  111. package/skills/generate-editor-search-query/SKILL.md +117 -0
  112. package/skills/generate-editor-search-query/references/open-search-window.md +70 -0
  113. package/skills/generate-editor-search-query/references/query-patterns.md +124 -0
  114. package/skills/implement-in-app-purchases/SKILL.md +82 -0
  115. package/skills/implement-in-app-purchases/references/api-details.md +41 -0
  116. package/skills/implement-in-app-purchases/references/api-notes.md +105 -0
  117. package/skills/implement-in-app-purchases/references/codeless-catalog.md +197 -0
  118. package/skills/implement-in-app-purchases/references/convert-adapty.md +270 -0
  119. package/skills/implement-in-app-purchases/references/convert-essentialkit.md +239 -0
  120. package/skills/implement-in-app-purchases/references/convert-revenuecat.md +277 -0
  121. package/skills/implement-in-app-purchases/references/convert-unipay.md +145 -0
  122. package/skills/implement-in-app-purchases/references/migration-v4-to-v5.md +46 -0
  123. package/skills/implement-in-app-purchases/references/path-add-iap-to-new-project.md +197 -0
  124. package/skills/implement-in-app-purchases/references/path-convert-native-google-billing.md +302 -0
  125. package/skills/implement-in-app-purchases/references/path-convert-native-storekit.md +357 -0
  126. package/skills/implement-in-app-purchases/references/path-implement-iap-d2c.md +253 -0
  127. package/skills/implement-in-app-purchases/references/platform-notes.md +55 -0
  128. package/skills/implement-in-app-purchases/references/pre-check.md +207 -0
  129. package/skills/implement-in-app-purchases/references/topic-map.md +46 -0
  130. package/skills/initialize-ai-navigation/SKILL.md +109 -0
  131. package/skills/initialize-ai-navigation/references/navigation-system.md +793 -0
  132. package/skills/initialize-ai-navigation/references/topic-map.md +66 -0
  133. package/skills/levelplay-unity-integration/SKILL.md +122 -0
  134. package/skills/levelplay-unity-integration/references/adding-more-ad-formats.md +15 -0
  135. package/skills/levelplay-unity-integration/references/banner-api.md +927 -0
  136. package/skills/levelplay-unity-integration/references/best-practices.md +604 -0
  137. package/skills/levelplay-unity-integration/references/dependency-resolution.md +94 -0
  138. package/skills/levelplay-unity-integration/references/examples.md +10 -0
  139. package/skills/levelplay-unity-integration/references/ilrd-api.md +337 -0
  140. package/skills/levelplay-unity-integration/references/initialization-api.md +851 -0
  141. package/skills/levelplay-unity-integration/references/interstitial-api.md +907 -0
  142. package/skills/levelplay-unity-integration/references/ios-setup.md +463 -0
  143. package/skills/levelplay-unity-integration/references/migration-sdk-9.md +666 -0
  144. package/skills/levelplay-unity-integration/references/privacy-settings.md +593 -0
  145. package/skills/levelplay-unity-integration/references/production-release-checklist.md +22 -0
  146. package/skills/levelplay-unity-integration/references/rewarded-api.md +918 -0
  147. package/skills/levelplay-unity-integration/references/step-0-new-or-migration.md +8 -0
  148. package/skills/levelplay-unity-integration/references/step-1-verify-environment.md +4 -0
  149. package/skills/levelplay-unity-integration/references/step-10-testing.md +15 -0
  150. package/skills/levelplay-unity-integration/references/step-2-business-goals.md +12 -0
  151. package/skills/levelplay-unity-integration/references/step-3-install-sdk.md +55 -0
  152. package/skills/levelplay-unity-integration/references/step-4-native-dependencies.md +22 -0
  153. package/skills/levelplay-unity-integration/references/step-5-app-key-and-ad-units.md +18 -0
  154. package/skills/levelplay-unity-integration/references/step-6-5-privacy-and-regulation.md +51 -0
  155. package/skills/levelplay-unity-integration/references/step-6-admob-keys.md +16 -0
  156. package/skills/levelplay-unity-integration/references/step-7-initialize.md +50 -0
  157. package/skills/levelplay-unity-integration/references/step-8-ad-unit-strategy.md +16 -0
  158. package/skills/levelplay-unity-integration/references/step-9-implement-ad-units.md +66 -0
  159. package/skills/levelplay-unity-integration/references/testing-and-validation.md +222 -0
  160. package/skills/levelplay-unity-integration/references/troubleshooting.md +99 -0
  161. package/skills/localize-unity-project/SKILL.md +159 -0
  162. package/skills/localize-unity-project/references/api-notes.md +15 -0
  163. package/skills/localize-unity-project/references/cjk-fonts.md +83 -0
  164. package/skills/localize-unity-project/references/docs-map.md +42 -0
  165. package/skills/localize-unity-project/references/documentation-lookup.md +33 -0
  166. package/skills/localize-unity-project/references/locale-switching.md +17 -0
  167. package/skills/localize-unity-project/references/table-completeness-check.md +47 -0
  168. package/skills/localize-unity-project/references/translating-existing-project.md +47 -0
  169. package/skills/localize-unity-project/references/translation-strategy.md +41 -0
  170. package/skills/localize-unity-project/references/troubleshooting.md +12 -0
  171. package/skills/localize-unity-project/scripts/L10nBatchProcessor.cs +191 -0
  172. package/skills/localize-unity-project/scripts/LocalizedFontAsset.cs +18 -0
  173. package/skills/manage-sprite-atlas/SKILL.md +202 -0
  174. package/skills/manage-sprite-atlas/references/addressables-delivery.md +58 -0
  175. package/skills/manage-sprite-atlas/references/advanced-manual-authoring.md +19 -0
  176. package/skills/manage-sprite-atlas/references/api.md +382 -0
  177. package/skills/manage-sprite-atlas/references/best-practices.md +183 -0
  178. package/skills/manage-sprite-atlas/references/common-errors.md +123 -0
  179. package/skills/manage-sprite-atlas/references/complete-workflow.md +61 -0
  180. package/skills/manage-sprite-atlas/references/custom-packing.md +147 -0
  181. package/skills/manage-sprite-atlas/references/delivery-mechanism.md +30 -0
  182. package/skills/manage-sprite-atlas/references/key-requirements.md +21 -0
  183. package/skills/manage-sprite-atlas/references/manual-authoring.md +39 -0
  184. package/skills/manage-sprite-atlas/references/namespaces.md +15 -0
  185. package/skills/manage-sprite-atlas/references/v2-architecture.md +21 -0
  186. package/skills/manage-sprite-atlas/resources/authoringvsruntime.cs +176 -0
  187. package/skills/manage-sprite-atlas/resources/buildaddressablespostprocess.cs +59 -0
  188. package/skills/manage-sprite-atlas/resources/cacheloadedatlases.cs +145 -0
  189. package/skills/manage-sprite-atlas/resources/cleanupbatchoperations.cs +119 -0
  190. package/skills/manage-sprite-atlas/resources/createandconfiguremasteratlas.cs +57 -0
  191. package/skills/manage-sprite-atlas/resources/custompackersetup.cs +88 -0
  192. package/skills/manage-sprite-atlas/resources/deprecatedmethods.cs +142 -0
  193. package/skills/manage-sprite-atlas/resources/dontmodifywithoutsaving.cs +120 -0
  194. package/skills/manage-sprite-atlas/resources/dontpackinruntimebuilds.cs +190 -0
  195. package/skills/manage-sprite-atlas/resources/dontscriptspriteatlasineditor.cs +114 -0
  196. package/skills/manage-sprite-atlas/resources/editorconfiguration.cs +95 -0
  197. package/skills/manage-sprite-atlas/resources/enablespritepacking.cs +172 -0
  198. package/skills/manage-sprite-atlas/resources/gridspritepacker.cs +50 -0
  199. package/skills/manage-sprite-atlas/resources/handlelatebinding.cs +147 -0
  200. package/skills/manage-sprite-atlas/resources/handlenullcases.cs +141 -0
  201. package/skills/manage-sprite-atlas/resources/migratev1tov2.cs +335 -0
  202. package/skills/manage-sprite-atlas/resources/multipagepacker.cs +80 -0
  203. package/skills/manage-sprite-atlas/resources/optimizetexturesettings.cs +191 -0
  204. package/skills/manage-sprite-atlas/resources/packerdebugging.cs +158 -0
  205. package/skills/manage-sprite-atlas/resources/packtransform.cs +51 -0
  206. package/skills/manage-sprite-atlas/resources/platformnames.cs +127 -0
  207. package/skills/manage-sprite-atlas/resources/queryspritesefficiently.cs +222 -0
  208. package/skills/manage-sprite-atlas/resources/registercallbacks.cs +152 -0
  209. package/skills/manage-sprite-atlas/resources/runtimeatlasloader.cs +15 -0
  210. package/skills/manage-sprite-atlas/resources/runtimespriteaccess.cs +19 -0
  211. package/skills/manage-sprite-atlas/resources/savespriteatlasasset.cs +107 -0
  212. package/skills/manage-sprite-atlas/resources/scriptablepacker.cs +81 -0
  213. package/skills/manage-sprite-atlas/resources/setincludeinbuild.cs +164 -0
  214. package/skills/manage-sprite-atlas/resources/sizeoptimizedpacker.cs +88 -0
  215. package/skills/manage-sprite-atlas/resources/spriteatlaslatebinding.cs +123 -0
  216. package/skills/manage-sprite-atlas/resources/spriteatlasprebuildgenerator.cs +219 -0
  217. package/skills/manage-sprite-atlas/resources/spritedata.cs +35 -0
  218. package/skills/manage-sprite-atlas/resources/spritepack.cs +39 -0
  219. package/skills/manage-sprite-atlas/resources/texturedata.cs +15 -0
  220. package/skills/manage-sprite-atlas/resources/updatebuildscripts.cs +357 -0
  221. package/skills/manage-sprite-atlas/resources/updatesettingsconfiguration.cs +135 -0
  222. package/skills/manage-sprite-atlas/resources/usevariantswisely.cs +179 -0
  223. package/skills/manage-sprite-atlas/resources/variantatlas.cs +39 -0
  224. package/skills/migrate-birp-to-urp/SECURITY.md +16 -0
  225. package/skills/migrate-birp-to-urp/SKILL.md +167 -0
  226. package/skills/migrate-birp-to-urp/references/capturing-the-editor.md +66 -0
  227. package/skills/migrate-birp-to-urp/references/complex-shader-situations.md +272 -0
  228. package/skills/migrate-birp-to-urp/references/conversion-and-rendering-triage.md +113 -0
  229. package/skills/migrate-birp-to-urp/references/custom-shader-triage.md +123 -0
  230. package/skills/migrate-birp-to-urp/references/implementation-patterns.md +529 -0
  231. package/skills/migrate-birp-to-urp/references/migration-workflow.md +265 -0
  232. package/skills/migrate-birp-to-urp/references/parity-validation-and-troubleshooting.md +114 -0
  233. package/skills/migrate-birp-to-urp/references/preflight-and-urp-setup.md +195 -0
  234. package/skills/migrate-birp-to-urp/references/quality-settings-map.md +69 -0
  235. package/skills/new-unity-project/SECURITY.md +26 -0
  236. package/skills/new-unity-project/SKILL.md +171 -0
  237. package/skills/new-unity-project/references/checklist.md +18 -0
  238. package/skills/new-unity-project/references/common-mistakes.md +15 -0
  239. package/skills/new-unity-project/references/skill-setup.md +66 -0
  240. package/skills/new-unity-project/references/step-1-concept.md +13 -0
  241. package/skills/new-unity-project/references/step-2-platforms-and-create.md +31 -0
  242. package/skills/new-unity-project/references/step-3-join-and-reconcile.md +16 -0
  243. package/skills/new-unity-project/references/step-4-source-control.md +9 -0
  244. package/skills/new-unity-project/references/step-5-packages.md +18 -0
  245. package/skills/new-unity-project/references/step-6-identity-and-floors.md +20 -0
  246. package/skills/new-unity-project/references/step-7-visual-baseline.md +75 -0
  247. package/skills/new-unity-project/references/step-8-first-commit.md +21 -0
  248. package/skills/new-unity-project/references/step-9-handoff.md +23 -0
  249. package/skills/optimize-audio/SECURITY.md +16 -0
  250. package/skills/optimize-audio/SKILL.md +120 -0
  251. package/skills/optimize-audio/references/diagnostics.md +42 -0
  252. package/skills/optimize-audio/references/troubleshooting.md +41 -0
  253. package/skills/optimize-audio/resources/audio-import-api.md +149 -0
  254. package/skills/optimize-audio/resources/platform-settings.md +48 -0
  255. package/skills/optimize-text-mesh-pro/SKILL.md +181 -0
  256. package/skills/optimize-text-mesh-pro/references/dynamic-atlas-and-profiling.md +28 -0
  257. package/skills/optimize-text-mesh-pro/references/topic-map.md +36 -0
  258. package/skills/optimize-web/SECURITY.md +16 -0
  259. package/skills/optimize-web/SKILL.md +157 -0
  260. package/skills/optimize-web/references/editor-csharp.md +120 -0
  261. package/skills/optimize-web/references/topic-map.md +49 -0
  262. package/skills/optimize-web/references/troubleshooting.md +45 -0
  263. package/skills/optimize-web/references/understand-request.md +19 -0
  264. package/skills/optimize-web/references/web-assets-quality.md +59 -0
  265. package/skills/optimize-web/references/web-player-settings.md +39 -0
  266. package/skills/optimize-web/references/web-profiling-memory.md +71 -0
  267. package/skills/optimize-web/scripts/WebOptimizer.cs +46 -0
  268. package/skills/optimize-web/scripts/toktx-examples.sh +11 -0
  269. package/skills/setup-multiplayer-services/SKILL.md +36 -0
  270. package/skills/setup-multiplayer-services/references/dgs-entrypoint.md +79 -0
  271. package/skills/setup-multiplayer-services/references/entrypoints.md +213 -0
  272. package/skills/setup-multiplayer-services/references/examples.md +33 -0
  273. package/skills/setup-multiplayer-services/references/implementation-fit.md +30 -0
  274. package/skills/setup-multiplayer-services/references/underlying-services.md +11 -0
  275. package/skills/setup-multiplayer-services/references/workflows-prerequisites.md +17 -0
  276. package/skills/setup-vivox-voice-chat/SECURITY.md +16 -0
  277. package/skills/setup-vivox-voice-chat/SKILL.md +166 -0
  278. package/skills/setup-vivox-voice-chat/references/api-reference-lookup.md +28 -0
  279. package/skills/setup-vivox-voice-chat/references/docs-topic-map.md +13 -0
  280. package/skills/setup-vivox-voice-chat/references/events-and-participants.md +79 -0
  281. package/skills/setup-vivox-voice-chat/references/init-and-login.md +92 -0
  282. package/skills/setup-vivox-voice-chat/references/text-chat.md +93 -0
  283. package/skills/setup-vivox-voice-chat/references/troubleshooting.md +47 -0
  284. package/skills/setup-vivox-voice-chat/references/voice-channels.md +92 -0
  285. package/skills/shader-graph-create-custom-node/SKILL.md +25 -0
  286. package/skills/shader-graph-create-custom-node/resources/all_hints.hlsl +182 -0
  287. package/skills/sprite-editor/SECURITY.md +16 -0
  288. package/skills/sprite-editor/SKILL.md +66 -0
  289. package/skills/sprite-editor/references/api_reference.md +151 -0
  290. package/skills/sprite-editor/references/background.md +112 -0
  291. package/skills/sprite-editor/references/templates.md +72 -0
  292. package/skills/sprite-editor/scripts/AutomaticSliceTexture.cs +40 -0
  293. package/skills/sprite-editor/scripts/GenerateNewSpriteRects.cs +200 -0
  294. package/skills/sprite-editor/scripts/GetTextureSourceImageSize.cs +32 -0
  295. package/skills/sprite-editor/scripts/GetTextureToSlice.cs +55 -0
  296. package/skills/sprite-editor/scripts/GridSliceTexture.cs +40 -0
  297. package/skills/sprite-editor/scripts/IsometricSliceTexture.cs +141 -0
  298. package/skills/sprite-editor/scripts/README.md +134 -0
  299. package/skills/sprite-editor/scripts/SetPivotExample.cs +58 -0
  300. package/skills/sprite-editor/scripts/SpriteToPng.cs +88 -0
  301. package/skills/sprite-segment-3x3grid/SKILL.md +177 -0
  302. package/skills/sprite-segment-3x3grid/references/topic-map.md +37 -0
  303. package/skills/sprite-segment-3x3grid/scripts/SpriteGridAnalysis.cs +119 -0
  304. package/skills/ui/SKILL.md +142 -0
  305. package/skills/ui-imgui/SKILL.md +180 -0
  306. package/skills/ui-imgui/references/gui-elements.md +156 -0
  307. package/skills/ui-imgui/references/templates.md +141 -0
  308. package/skills/ui-ugui/SKILL.md +217 -0
  309. package/skills/ui-ugui/references/layout-and-anchors.md +61 -0
  310. package/skills/ui-ugui/references/scrollview-setup.md +29 -0
  311. package/skills/ui-uitk/SKILL.md +229 -0
  312. package/skills/ui-uitk/references/common-issues.md +74 -0
  313. package/skills/ui-uitk/references/custom-elements.md +241 -0
  314. package/skills/ui-uitk/references/painter2d.md +282 -0
  315. package/skills/ui-uitk/references/pointermanipulator-guide.md +94 -0
  316. package/skills/ui-uitk/references/svg-icons.md +136 -0
  317. package/skills/ui-uitk/references/ui-runtime-binding.md +234 -0
  318. package/skills/ui-uitk/references/uss-guide.md +138 -0
  319. package/skills/unity-cli/CHANGELOG.md +524 -0
  320. package/skills/unity-cli/SECURITY.md +68 -0
  321. package/skills/unity-cli/SKILL.md +160 -0
  322. package/skills/unity-cli/references/auth-license-cloud.md +318 -0
  323. package/skills/unity-cli/references/bootstrap-project.md +177 -0
  324. package/skills/unity-cli/references/build-run-test.md +555 -0
  325. package/skills/unity-cli/references/catalog-plugins.md +86 -0
  326. package/skills/unity-cli/references/ci-recipes.md +76 -0
  327. package/skills/unity-cli/references/cloud-automation.md +819 -0
  328. package/skills/unity-cli/references/cloud-workflows.md +19 -0
  329. package/skills/unity-cli/references/collaboration.md +533 -0
  330. package/skills/unity-cli/references/config-hub.md +239 -0
  331. package/skills/unity-cli/references/diagnostics-maintenance.md +718 -0
  332. package/skills/unity-cli/references/docs-workflow.md +179 -0
  333. package/skills/unity-cli/references/editor-targeting.md +29 -0
  334. package/skills/unity-cli/references/editors-install.md +490 -0
  335. package/skills/unity-cli/references/environment-variables.md +42 -0
  336. package/skills/unity-cli/references/exit-codes.md +47 -0
  337. package/skills/unity-cli/references/global-flags.md +32 -0
  338. package/skills/unity-cli/references/integration-advanced.md +1130 -0
  339. package/skills/unity-cli/references/notes.md +20 -0
  340. package/skills/unity-cli/references/notifications.md +39 -0
  341. package/skills/unity-cli/references/playmode-verification-loop.md +279 -0
  342. package/skills/unity-cli/references/projects-templates.md +997 -0
  343. package/skills/unity-cli/references/scene-editing-preflight.md +25 -0
  344. package/skills/unity-cli/references/setup-and-help.md +54 -0
  345. package/skills/unity-cli/references/version-control.md +612 -0
  346. package/skills/unity-package-management/SKILL.md +169 -0
  347. package/skills/unity-package-management/references/discovering-packages.md +66 -0
  348. package/skills/unity-package-management/references/headless-run.md +31 -0
  349. package/skills/unity-package-management/references/installer-script.md +92 -0
  350. package/skills/unity-package-management/references/select-packages.md +126 -0
  351. package/skills/unity-pipeline/SECURITY.md +17 -0
  352. package/skills/unity-pipeline/SKILL.md +205 -0
  353. package/skills/unity-pipeline/references/csharp-eval.md +23 -0
  354. package/skills/unity-pipeline/references/multiplayer-play-mode.md +103 -0
  355. package/skills/urp-postprocessing/SECURITY.md +16 -0
  356. package/skills/urp-postprocessing/SKILL.md +188 -0
  357. package/skills/urp-postprocessing/references/code-templates.md +150 -0
  358. package/skills/urp-postprocessing/references/effect-reference.md +86 -0
  359. package/skills/validate-urp-render-graph-renderer-feature/SKILL.md +92 -0
  360. package/skills/validate-urp-render-graph-renderer-feature/references/validation-checklist.md +179 -0
@@ -0,0 +1,904 @@
1
+ # Battle Pass Blueprint
2
+
3
+ ## Table of Contents
4
+
5
+ - [Architecture Overview](#architecture-overview)
6
+ - [Free Track vs Premium Track](#free-track-vs-premium-track)
7
+ - [Multiple Concurrent Passes](#multiple-concurrent-passes)
8
+ - [Data Models](#data-models)
9
+ - [API Notes](#api-notes)
10
+ - [Namespace Aliases](#namespace-aliases-required)
11
+ - [ProtectedReadAccessClassOptions](#protectedreadaccessclassoptions)
12
+ - [Why Tier Thresholds Live in the Module](#why-tier-thresholds-live-in-the-module)
13
+ - [Source Whitelist Enforcement](#source-whitelist-enforcement)
14
+ - [Error Handling](#error-handling)
15
+ - [Cloud Code Module Functions](#cloud-code-module-functions)
16
+ - [Load Pass Definitions from Remote Config](#load-pass-definitions-from-remote-config)
17
+ - [Load Player Progress](#load-player-progress)
18
+ - [Client to Cloud Code Calls](#client-to-cloud-code-calls)
19
+ - [Full Client Implementation](#full-client-implementation)
20
+ - [BattlePassTester](#battlepasstester)
21
+ - [Cloud Code Module Implementation](#cloud-code-module-implementation)
22
+ - [BattlePassModule.cs](#battlepassmodulecs)
23
+ - [Server-Side Models](#server-side-models-inside-battlepassmodulecs)
24
+ - [Cloud Resources](#cloud-resources)
25
+ - [Deployment Checklist](#deployment-checklist)
26
+ - [Validation](#validation)
27
+ - [Asset Store & Sample Projects](#asset-store--sample-projects)
28
+
29
+ ---
30
+
31
+ ## Architecture Overview
32
+
33
+ | Concern | Service | Key |
34
+ |---|---|---|
35
+ | Pass definitions | Remote Config | `"battle_passes"` |
36
+ | Per-player progress | Cloud Save (Protected) | `"battle_pass_progress"` |
37
+ | Award XP | Cloud Code | `BattlePassModule.AwardXp` |
38
+ | Claim reward | Cloud Code | `BattlePassModule.ClaimReward` |
39
+ | Purchase premium | Cloud Code | `BattlePassModule.PurchasePremium` (integration stub; always throws until you add purchase validation) |
40
+
41
+ ```
42
+ Remote Config Cloud Save (Protected) Cloud Code Module
43
+ "battle_passes" "battle_pass_progress" BattlePassModule
44
+ | | |
45
+ v v v
46
+ BattlePassDefinition[] { passId: Progress } AwardXp
47
+ PassId, Name, Server-writes only ClaimReward
48
+ StartDate, EndDate, PurchasePremium
49
+ Tiers[]
50
+ | ^ |
51
+ | | |
52
+ +------- client reads -------+------- server writes ---------+
53
+ ```
54
+
55
+ **Key principle:** The client reads pass definitions from Remote Config and player progress from Cloud Save (Protected bucket). All mutations -- awarding XP, claiming rewards, purchasing premium -- go through the Cloud Code module, which is the only writer to the Protected bucket.
56
+
57
+ ---
58
+
59
+ ## Free Track vs Premium Track
60
+
61
+ Every tier in a battle pass has **two parallel reward tracks**:
62
+
63
+ - **Free track** -- available to all players who reach the tier.
64
+ - **Premium track** -- visible to all players but **locked** unless the player has purchased the premium pass (`HasPremium = true`).
65
+
66
+ Premium rewards are displayed in the UI alongside free rewards so non-premium players can see what they are missing, encouraging upgrade. The Cloud Code module enforces the lock: `ClaimReward` with `rewardType = "premium"` throws if `HasPremium` is false.
67
+
68
+ ---
69
+
70
+ ## Multiple Concurrent Passes
71
+
72
+ The system supports multiple battle passes running simultaneously:
73
+
74
+ - Each pass has a unique `PassId` (e.g. `"season_03"`, `"easter_2026"`).
75
+ - Passes may overlap in time. A player can have active progress in several passes at once.
76
+ - Progress and premium status are tracked **independently per pass**. The Cloud Save key `"battle_pass_progress"` stores a `Dictionary<string, BattlePassProgress>` keyed by `PassId`.
77
+ - **Date filtering is client-side.** The client fetches all pass definitions from Remote Config and filters to those whose `StartDate <= now <= EndDate`.
78
+ - Expired passes remain in the progress dictionary. The client simply stops showing them. Clean up stale entries via a scheduled Cloud Code trigger if desired.
79
+
80
+ ---
81
+
82
+ ## Data Models
83
+
84
+ ### BattlePassDefinition (client-side, from Remote Config)
85
+
86
+ ```csharp
87
+ [Serializable]
88
+ public class BattlePassDefinition
89
+ {
90
+ public string PassId;
91
+ public string Name;
92
+ public string StartDate; // ISO 8601, e.g. "2026-04-01T00:00:00Z"
93
+ public string EndDate; // ISO 8601
94
+ public List<BattlePassTier> Tiers;
95
+ }
96
+ ```
97
+
98
+ ### BattlePassTier (client-side, from Remote Config)
99
+
100
+ ```csharp
101
+ [Serializable]
102
+ public class BattlePassTier
103
+ {
104
+ public int TierNumber;
105
+ public int XpRequired; // cumulative XP to reach this tier (not delta from previous)
106
+ public string FreeRewardId;
107
+ public string PremiumRewardId;
108
+ }
109
+ ```
110
+
111
+ ### BattlePassProgress (defined once, in the cloud assembly)
112
+
113
+ Stored in Cloud Save Protected, one entry per pass, keyed by `PassId` in the progress dictionary at Cloud Save key `"battle_pass_progress"`. Define it once, in `BattlePassModule.cs` (see [Server-Side Models](#server-side-models-inside-battlepassmodulecs)), as a class of public settable members. The source generator emits a data-only stub of the same name and namespace, `BattlePassModule.Cloud.BattlePassProgress`, into the client assembly, so client code uses that type and never declares its own copy. `ClaimedRewards` holds keys such as `"tier_1_free"` and `"tier_2_premium"`.
114
+
115
+ `BattlePassTierThreshold` is a server-only helper that mirrors the `Tiers` in `BattlePasses.rc`. It never appears in an endpoint signature, so it has no client stub.
116
+
117
+ ---
118
+
119
+ ## API Notes
120
+
121
+ ### Namespace Aliases (Required)
122
+
123
+ Cloud Save has multiple types named `LoadOptions`, `SaveOptions`, and `DeleteOptions` across
124
+ different namespaces. Always declare these aliases at the top of any file that uses Cloud Save:
125
+
126
+ ```csharp
127
+ using PlayerLoadOptions = Unity.Services.CloudSave.Models.Data.Player.LoadOptions;
128
+ using PlayerSaveOptions = Unity.Services.CloudSave.Models.Data.Player.SaveOptions;
129
+ using PlayerDeleteOptions = Unity.Services.CloudSave.Models.Data.Player.DeleteOptions;
130
+ ```
131
+
132
+ Without these aliases, the compiler will report ambiguous type references.
133
+
134
+ ### ProtectedReadAccessClassOptions
135
+
136
+ Use `ProtectedReadAccessClassOptions` when loading player progress. This reads the current
137
+ player's own Protected data — no player ID parameter is required; it always reads the signed-in
138
+ player's data.
139
+
140
+ > **Common mistake:** Reading from the wrong access class (e.g. using default `LoadOptions`
141
+ > instead of `ProtectedReadAccessClassOptions`) will return no data, because the progress
142
+ > is stored in the Protected bucket by the Cloud Code module.
143
+
144
+ ### Why Tier Thresholds Live in the Module
145
+
146
+ Remote Config is not accessible from inside Cloud Code modules, and anything the client sends is
147
+ untrusted: a client that supplies its own tier thresholds can set a tier-three threshold of zero
148
+ and reach tier three with 1 XP. The module therefore holds the thresholds in a server-side
149
+ constant (`PassTiers` in `BattlePassModule.cs`) and `AwardXp` takes no tier data from the client.
150
+ Pass dates get the same treatment: `PassWindows` holds each pass's UTC start and end, and `AwardXp`
151
+ and `ClaimReward` reject any pass outside `[Start, End)` using server time, so an expired pass cannot
152
+ gain XP and a staged future pass cannot be claimed early. Rewards must be claimed while the pass is
153
+ active. Keep `PassTiers` and `PassWindows` in sync with the `XpRequired`, `StartDate` and `EndDate`
154
+ values in `BattlePasses.rc` whenever you change a pass. Passes missing from either are rejected.
155
+
156
+ ### Source Whitelist Enforcement
157
+
158
+ The Cloud Code module maintains a server-side whitelist of allowed XP source strings:
159
+
160
+ ```csharp
161
+ static readonly Dictionary<string, int> MaxXpPerSource = new()
162
+ {
163
+ ["match_complete"] = 200,
164
+ ["daily_login"] = 50,
165
+ ["quest_complete"] = 300
166
+ };
167
+ ```
168
+
169
+ Any `AwardXp` call with an unrecognized source throws an exception, and each source has a
170
+ per-call XP cap (`MaxXpPerSource`). Add or remove entries as needed for your game. The whitelist
171
+ and caps prevent clients from inventing XP sources or awarding themselves arbitrary amounts.
172
+ For full protection, derive XP from server-verified events instead of a client-supplied `amount`.
173
+
174
+ ### Error Handling
175
+
176
+ Cloud Code and Cloud Save errors surface as typed exceptions on the client:
177
+
178
+ ```csharp
179
+ try
180
+ {
181
+ var progress = await AwardXpAsync(passId, 100, "match_complete");
182
+ }
183
+ catch (CloudCodeException ex)
184
+ {
185
+ // ex.Reason: CloudCodeExceptionReason (e.g. ScriptError for module-thrown exceptions)
186
+ Debug.LogError($"[BattlePass] Cloud Code error: {ex.Reason} — {ex.Message}");
187
+ }
188
+ catch (CloudSaveException ex)
189
+ {
190
+ // ex.Reason: CloudSaveExceptionReason
191
+ // A 403 on the Protected bucket means the client attempted a direct write —
192
+ // all writes must go through the Cloud Code module.
193
+ Debug.LogError($"[BattlePass] Cloud Save error: {ex.Reason} — {ex.Message}");
194
+ }
195
+ ```
196
+
197
+ | Error | Cause | Fix |
198
+ | :-- | :-- | :-- |
199
+ | `CloudCodeException` with reason `ScriptError` | The module threw, for example on a failed validation | Read `ex.Message`, then correct the arguments or the player's state |
200
+ | `CloudSaveException` with a `403` | The client attempted a direct write to the Protected bucket | Route the write through the Cloud Code module |
201
+
202
+ ---
203
+
204
+ ## Cloud Code Module Functions
205
+
206
+ | Function | Parameters | Behavior |
207
+ |---|---|---|
208
+ | `AwardXp` | `passId`, `amount`, `source` | Validates `source` against a whitelist (`MaxXpPerSource` keys), rejects non-positive amounts and amounts above the source's cap, rejects unknown passes, adds XP to the pass progress, recalculates the current tier from the server-side `PassTiers`, saves to Cloud Save Protected |
209
+ | `ClaimReward` | `passId`, `tierNumber`, `rewardType` | Validates the tier exists in the server-side `PassTiers` and the player's XP meets its threshold, validates premium ownership if `rewardType` is `"premium"`, checks reward is not already claimed, records the claim as `"tier_N_type"` (e.g. `"tier_2_premium"`), saves to Cloud Save Protected |
210
+ | `PurchasePremium` | `passId` | **Integration stub.** Returns the existing progress if `HasPremium` is already true; otherwise the sample always throws, because nothing in the blueprint sets `HasPremium`. Add server-side purchase validation that sets `HasPremium = true` before premium purchasing works; do not deploy this path as-is. It fails closed and never grants premium unverified. |
211
+
212
+ All three functions return the updated `BattlePassProgress` for the given pass.
213
+
214
+ ---
215
+
216
+ ## Load Pass Definitions from Remote Config
217
+
218
+ ```csharp
219
+ struct UserAttributes {}
220
+ struct AppAttributes {}
221
+
222
+ async Task<List<BattlePassDefinition>> LoadActivePassesAsync()
223
+ {
224
+ var result = await RemoteConfigService.Instance.FetchConfigsAsync(
225
+ new UserAttributes(), new AppAttributes());
226
+
227
+ var token = result.config["battle_passes"];
228
+ if (token == null) return new List<BattlePassDefinition>();
229
+
230
+ var allPasses = token.ToObject<List<BattlePassDefinition>>();
231
+
232
+ var now = DateTime.UtcNow;
233
+ return allPasses
234
+ .Where(p =>
235
+ DateTime.Parse(p.StartDate, null, DateTimeStyles.RoundtripKind) <= now &&
236
+ DateTime.Parse(p.EndDate, null, DateTimeStyles.RoundtripKind) >= now)
237
+ .ToList();
238
+ }
239
+ ```
240
+
241
+ - `FetchConfigsAsync` downloads all Remote Config entries.
242
+ - `result.config["battle_passes"]` returns a `JToken` for the JSON array stored under that key.
243
+ - Date filtering is performed client-side by comparing `StartDate` and `EndDate` against `DateTime.UtcNow`.
244
+
245
+ ---
246
+
247
+ ## Load Player Progress
248
+
249
+ ```csharp
250
+ using Unity.Services.CloudSave;
251
+ using Unity.Services.CloudSave.Models;
252
+ using Unity.Services.CloudSave.Models.Data.Player;
253
+ using PlayerLoadOptions = Unity.Services.CloudSave.Models.Data.Player.LoadOptions;
254
+
255
+ async Task<Dictionary<string, BattlePassProgress>> LoadProgressAsync()
256
+ {
257
+ var result = await CloudSaveService.Instance.Data.Player.LoadAsync(
258
+ new HashSet<string> { "battle_pass_progress" },
259
+ new PlayerLoadOptions(new ProtectedReadAccessClassOptions()));
260
+
261
+ if (!result.TryGetValue("battle_pass_progress", out var item))
262
+ return new Dictionary<string, BattlePassProgress>();
263
+
264
+ return JsonConvert.DeserializeObject<Dictionary<string, BattlePassProgress>>(
265
+ item.Value.GetAsString())
266
+ ?? new Dictionary<string, BattlePassProgress>();
267
+ }
268
+ ```
269
+
270
+ **Important:** Use `ProtectedReadAccessClassOptions` because the Cloud Code module writes to the Protected bucket. Reading from the wrong access class will return no data.
271
+
272
+ > **Stale entries:** Expired pass entries remain in the dictionary indefinitely but are inert —
273
+ > the client ignores any PassId not present in the current active pass list.
274
+
275
+ ---
276
+
277
+ ## Client to Cloud Code Calls
278
+
279
+ ### AwardXpAsync
280
+
281
+ ```csharp
282
+ public async Task<BattlePassProgress> AwardXpAsync(string passId, int amount, string source)
283
+ {
284
+ if (!ActivePasses.Any(p => p.PassId == passId))
285
+ throw new InvalidOperationException(
286
+ $"Pass '{passId}' is not in the active passes list. Call LoadAsync first.");
287
+
288
+ var progress = await m_Client.AwardXp(passId, amount, source);
289
+
290
+ Progress[passId] = progress;
291
+ return progress;
292
+ }
293
+ ```
294
+
295
+ The client sends no tier data; the server recalculates the player's tier from its own `PassTiers`. The active-pass check above is UX only; the module enforces pass dates itself. `m_Client` is the generated `BattlePassModuleClient`, created after sign-in.
296
+
297
+ ### ClaimRewardAsync
298
+
299
+ ```csharp
300
+ public async Task<BattlePassProgress> ClaimRewardAsync(
301
+ string passId, int tierNumber, string rewardType)
302
+ {
303
+ var progress = await m_Client.ClaimReward(passId, tierNumber, rewardType);
304
+
305
+ Progress[passId] = progress;
306
+ return progress;
307
+ }
308
+ ```
309
+
310
+ ### PurchasePremiumAsync
311
+
312
+ Calls the `PurchasePremium` integration stub. It throws for any player without `HasPremium` until you add a validated purchase flow to the module.
313
+
314
+ ```csharp
315
+ public async Task<BattlePassProgress> PurchasePremiumAsync(string passId)
316
+ {
317
+ var progress = await m_Client.PurchasePremium(passId);
318
+
319
+ Progress[passId] = progress;
320
+ return progress;
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Full Client Implementation
327
+
328
+ `BattlePassManager` is a `MonoBehaviour` that initializes services, loads pass definitions and player progress, and exposes methods for XP awards, reward claims, and premium purchases. Attach it to a GameObject in your scene.
329
+
330
+ ```csharp
331
+ using System;
332
+ using System.Collections.Generic;
333
+ using System.Globalization;
334
+ using System.Linq;
335
+ using System.Threading.Tasks;
336
+ using BattlePassModule.Client;
337
+ using BattlePassModule.Cloud;
338
+ using Newtonsoft.Json;
339
+ using Unity.Services.Authentication;
340
+ using Unity.Services.CloudSave;
341
+ using Unity.Services.CloudSave.Models;
342
+ using Unity.Services.CloudSave.Models.Data.Player;
343
+ using PlayerLoadOptions = Unity.Services.CloudSave.Models.Data.Player.LoadOptions;
344
+ using Unity.Services.Core;
345
+ using Unity.Services.RemoteConfig;
346
+ using UnityEngine;
347
+
348
+ public class BattlePassManager : MonoBehaviour
349
+ {
350
+ BattlePassModuleClient m_Client;
351
+
352
+ public List<BattlePassDefinition> ActivePasses { get; private set; } = new();
353
+ public Dictionary<string, BattlePassProgress> Progress { get; private set; } = new();
354
+
355
+ public event Action Loaded;
356
+
357
+ async void Start()
358
+ {
359
+ try
360
+ {
361
+ await UnityServices.InitializeAsync();
362
+ if (!AuthenticationService.Instance.IsSignedIn)
363
+ {
364
+ await AuthenticationService.Instance.SignInAnonymouslyAsync();
365
+ }
366
+ m_Client = new BattlePassModuleClient();
367
+ await LoadAsync();
368
+ Debug.Log($"BattlePassManager: Loaded {ActivePasses.Count} passes.");
369
+ Loaded?.Invoke();
370
+ }
371
+ catch (Exception ex)
372
+ {
373
+ Debug.LogError($"BattlePassManager: Error during loading: {ex.Message}\n{ex.StackTrace}");
374
+ }
375
+ }
376
+
377
+ public async Task LoadAsync()
378
+ {
379
+ ActivePasses = await LoadActivePassesAsync();
380
+ Progress = await LoadProgressAsync();
381
+ }
382
+
383
+ struct UserAttributes {}
384
+ struct AppAttributes {}
385
+
386
+ async Task<List<BattlePassDefinition>> LoadActivePassesAsync()
387
+ {
388
+ var result = await RemoteConfigService.Instance.FetchConfigsAsync(
389
+ new UserAttributes(), new AppAttributes());
390
+
391
+ var token = result.config["battle_passes"];
392
+ if (token == null) return new List<BattlePassDefinition>();
393
+
394
+ var allPasses = token.ToObject<List<BattlePassDefinition>>();
395
+
396
+ var now = DateTime.UtcNow;
397
+ return allPasses
398
+ .Where(p =>
399
+ DateTime.Parse(p.StartDate, null, DateTimeStyles.RoundtripKind) <= now &&
400
+ DateTime.Parse(p.EndDate, null, DateTimeStyles.RoundtripKind) >= now)
401
+ .ToList();
402
+ }
403
+
404
+ async Task<Dictionary<string, BattlePassProgress>> LoadProgressAsync()
405
+ {
406
+ var result = await CloudSaveService.Instance.Data.Player.LoadAsync(
407
+ new HashSet<string> { "battle_pass_progress" },
408
+ new PlayerLoadOptions(new ProtectedReadAccessClassOptions()));
409
+
410
+ if (!result.TryGetValue("battle_pass_progress", out var item))
411
+ return new Dictionary<string, BattlePassProgress>();
412
+
413
+ return JsonConvert.DeserializeObject<Dictionary<string, BattlePassProgress>>(
414
+ item.Value.GetAsString())
415
+ ?? new Dictionary<string, BattlePassProgress>();
416
+ }
417
+
418
+ public async Task<BattlePassProgress> AwardXpAsync(string passId, int amount, string source)
419
+ {
420
+ if (!ActivePasses.Any(p => p.PassId == passId))
421
+ throw new InvalidOperationException(
422
+ $"Pass '{passId}' is not in the active passes list. Call LoadAsync first.");
423
+
424
+ var progress = await m_Client.AwardXp(passId, amount, source);
425
+
426
+ Progress[passId] = progress;
427
+ return progress;
428
+ }
429
+
430
+ public async Task<BattlePassProgress> ClaimRewardAsync(
431
+ string passId, int tierNumber, string rewardType)
432
+ {
433
+ var progress = await m_Client.ClaimReward(passId, tierNumber, rewardType);
434
+
435
+ Progress[passId] = progress;
436
+ return progress;
437
+ }
438
+
439
+ // Requires the PurchasePremium integration stub to be completed server-side.
440
+ public async Task<BattlePassProgress> PurchasePremiumAsync(string passId)
441
+ {
442
+ var progress = await m_Client.PurchasePremium(passId);
443
+
444
+ Progress[passId] = progress;
445
+ return progress;
446
+ }
447
+ }
448
+ ```
449
+
450
+ ---
451
+
452
+ ## BattlePassTester
453
+
454
+ Attach to the same GameObject as `BattlePassManager` in your test scene. Wire `m_Manager` via
455
+ the Inspector, or let it fall back to `GetComponent`.
456
+
457
+ ```csharp
458
+ using System.Text;
459
+ using UnityEngine;
460
+
461
+ /// <summary>
462
+ /// Attach alongside BattlePassManager to run a quick in-Editor smoke test.
463
+ /// On load: logs all active pass states.
464
+ /// Then: awards 100 XP to the first active pass via Cloud Code and logs the result.
465
+ /// </summary>
466
+ public class BattlePassTester : MonoBehaviour
467
+ {
468
+ [SerializeField] BattlePassManager m_Manager;
469
+
470
+ void Start()
471
+ {
472
+ if (m_Manager == null)
473
+ m_Manager = GetComponent<BattlePassManager>();
474
+ m_Manager.Loaded += OnLoaded;
475
+ }
476
+
477
+ async void OnLoaded()
478
+ {
479
+ LogState("Initial state");
480
+
481
+ if (m_Manager.ActivePasses.Count == 0)
482
+ {
483
+ Debug.Log("[BattlePassTester] No active passes — nothing to award.");
484
+ return;
485
+ }
486
+
487
+ var firstPass = m_Manager.ActivePasses[0];
488
+ Debug.Log($"[BattlePassTester] Awarding 100 XP to '{firstPass.PassId}' " +
489
+ $"(source: match_complete) ...");
490
+
491
+ var result = await m_Manager.AwardXpAsync(firstPass.PassId, 100, "match_complete");
492
+
493
+ Debug.Log($"[BattlePassTester] After award — " +
494
+ $"XP={result.CurrentXp}, Tier={result.CurrentTier}");
495
+ }
496
+
497
+ void LogState(string label)
498
+ {
499
+ var sb = new StringBuilder();
500
+ sb.AppendLine($"[BattlePassTester] {label} — {m_Manager.ActivePasses.Count} active pass(es):");
501
+
502
+ foreach (var pass in m_Manager.ActivePasses)
503
+ {
504
+ m_Manager.Progress.TryGetValue(pass.PassId, out var progress);
505
+ sb.AppendLine($" [{pass.PassId}] {pass.Name}: " +
506
+ $"XP={progress?.CurrentXp ?? 0}, " +
507
+ $"Tier={progress?.CurrentTier ?? 1}, " +
508
+ $"Premium={progress?.HasPremium ?? false}, " +
509
+ $"Claimed={progress?.ClaimedRewards?.Count ?? 0}");
510
+ }
511
+
512
+ Debug.Log(sb.ToString());
513
+ }
514
+ }
515
+ ```
516
+
517
+ ---
518
+
519
+ ## Cloud Code Module Implementation
520
+
521
+ > **Create the module with the Editor, not by hand.** Right-click a folder under `Assets/` ->
522
+ > **Create > Services > Cloud Code Module Script** and name it `BattlePassModule`. See
523
+ > the Creating a Cloud Code Module section of `references/cloud-code-modules.md`.
524
+ >
525
+ > The Editor generates the following. Use the list to check what you got — there is nothing here to
526
+ > type out, and no `.sln`, `.csproj`, `.pubxml` or test project:
527
+ >
528
+ > - [ ] `Assets/CloudCode/BattlePassModule/BattlePassModule.ccmu` (holds the two asmdef GUIDs)
529
+ > - [ ] `Cloud/BattlePassModule.asmdef` — Editor-only, `noEngineReferences`, `autoReferenced: false`
530
+ > - [ ] `Cloud/BattlePassModule.cs` — rename/replace with the `[CloudCodeFunction]` endpoints below
531
+ > - [ ] `Client/BattlePassModuleClient.asmdef` — references the cloud assembly
532
+ > - [ ] `Client/BattlePassModuleClient.cs` — `[CloudModuleClient(typeof(BattlePassModule))]` partial
533
+ >
534
+ > One file the Editor does **not** generate, which this module needs because its endpoints inject
535
+ > `IGameApiClient`:
536
+ >
537
+ > - [ ] `Cloud/CloudSetup.cs` — an `ICloudCodeSetup` calling `config.AddGameApiClient()`
538
+ > - [ ] `Newtonsoft.Json.dll` added to `precompiledReferences` in `Cloud/BattlePassModule.asmdef` — the generated asmdef ships that array empty, and this module's code uses `JsonConvert` (see `references/cloud-code-modules.md`)
539
+ >
540
+ > The deployed module name is the **cloud asmdef's name** (`BattlePassModule`), not the `.ccmu` filename.
541
+ >
542
+ > The client calls the module through the generated `BattlePassModuleClient`. The assembly that holds `BattlePassManager` must reference the module's client assembly; the Editor creates it auto-referenced, so the default game assembly already does.
543
+
544
+ ### BattlePassModule.cs
545
+
546
+ ```csharp
547
+ using System;
548
+ using System.Collections.Generic;
549
+ using System.Linq;
550
+ using System.Threading.Tasks;
551
+ using Newtonsoft.Json;
552
+ using Unity.Services.CloudCode.Apis;
553
+ using Unity.Services.CloudCode.Core;
554
+ using Unity.Services.CloudSave.Model;
555
+
556
+ namespace BattlePassModule.Cloud
557
+ {
558
+ public class BattlePassModule
559
+ {
560
+ const string ProgressKey = "battle_pass_progress";
561
+
562
+ static readonly Dictionary<string, int> MaxXpPerSource = new()
563
+ {
564
+ ["match_complete"] = 200,
565
+ ["daily_login"] = 50,
566
+ ["quest_complete"] = 300
567
+ };
568
+
569
+ // Keep in sync with the Tiers in BattlePasses.rc; clients are never trusted for thresholds.
570
+ static readonly Dictionary<string, List<BattlePassTierThreshold>> PassTiers = new()
571
+ {
572
+ ["season_03"] = new()
573
+ {
574
+ new BattlePassTierThreshold { TierNumber = 1, XpRequired = 0 },
575
+ new BattlePassTierThreshold { TierNumber = 2, XpRequired = 500 },
576
+ new BattlePassTierThreshold { TierNumber = 3, XpRequired = 1200 }
577
+ },
578
+ ["easter_2026"] = new()
579
+ {
580
+ new BattlePassTierThreshold { TierNumber = 1, XpRequired = 0 },
581
+ new BattlePassTierThreshold { TierNumber = 2, XpRequired = 300 }
582
+ }
583
+ };
584
+
585
+ // Keep in sync with StartDate/EndDate in BattlePasses.rc. UTC, End is exclusive.
586
+ static readonly Dictionary<string, (DateTime Start, DateTime End)> PassWindows = new()
587
+ {
588
+ ["season_03"] = (new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
589
+ new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc)),
590
+ ["easter_2026"] = (new DateTime(2026, 4, 1, 0, 0, 0, DateTimeKind.Utc),
591
+ new DateTime(2026, 4, 22, 0, 0, 0, DateTimeKind.Utc))
592
+ };
593
+
594
+ // ── AwardXp ──────────────────────────────────────────────────────────────
595
+
596
+ [CloudCodeFunction("AwardXp")]
597
+ public async Task<BattlePassProgress> AwardXp(
598
+ [CloudCodeService] IExecutionContext context, [CloudCodeService] IGameApiClient client,
599
+ string passId, int amount, string source)
600
+ {
601
+ if (!MaxXpPerSource.TryGetValue(source, out var maxXp))
602
+ throw new Exception(
603
+ $"Invalid XP source: '{source}'. Allowed: {string.Join(", ", MaxXpPerSource.Keys)}");
604
+
605
+ if (amount <= 0 || amount > maxXp)
606
+ throw new Exception($"XP amount must be between 1 and {maxXp} for source '{source}'.");
607
+
608
+ if (!PassTiers.TryGetValue(passId, out var tiers))
609
+ throw new Exception($"Unknown pass '{passId}'.");
610
+
611
+ RequireActive(passId);
612
+
613
+ var allProgress = await LoadProgressAsync(context, client);
614
+
615
+ if (!allProgress.TryGetValue(passId, out var progress))
616
+ progress = new BattlePassProgress { CurrentTier = 1, ClaimedRewards = new List<string>() };
617
+
618
+ progress.CurrentXp += amount;
619
+
620
+ progress.CurrentTier = CalculateTier(progress.CurrentXp, tiers);
621
+
622
+ allProgress[passId] = progress;
623
+ await SaveProgressAsync(context, client, allProgress);
624
+
625
+ return progress;
626
+ }
627
+
628
+ // ── ClaimReward ──────────────────────────────────────────────────────────
629
+
630
+ [CloudCodeFunction("ClaimReward")]
631
+ public async Task<BattlePassProgress> ClaimReward(
632
+ [CloudCodeService] IExecutionContext context, [CloudCodeService] IGameApiClient client,
633
+ string passId, int tierNumber, string rewardType)
634
+ {
635
+ if (rewardType != "free" && rewardType != "premium")
636
+ throw new Exception(
637
+ $"Invalid rewardType: '{rewardType}'. Must be 'free' or 'premium'.");
638
+
639
+ RequireActive(passId);
640
+
641
+ var allProgress = await LoadProgressAsync(context, client);
642
+
643
+ if (!allProgress.TryGetValue(passId, out var progress))
644
+ throw new Exception($"No progress found for pass '{passId}'. Award XP first.");
645
+
646
+ if (!PassTiers.TryGetValue(passId, out var tiers))
647
+ throw new Exception($"Unknown pass '{passId}'.");
648
+
649
+ var tier = tiers.FirstOrDefault(t => t.TierNumber == tierNumber)
650
+ ?? throw new Exception($"Tier {tierNumber} does not exist in pass '{passId}'.");
651
+
652
+ if (progress.CurrentXp < tier.XpRequired)
653
+ throw new Exception(
654
+ $"Tier {tierNumber} not yet reached ({progress.CurrentXp}/{tier.XpRequired} XP).");
655
+
656
+ if (rewardType == "premium" && !progress.HasPremium)
657
+ throw new Exception("Premium pass not owned. Purchase premium first.");
658
+
659
+ var rewardKey = $"tier_{tierNumber}_{rewardType}";
660
+ if (progress.ClaimedRewards.Contains(rewardKey))
661
+ throw new Exception($"Reward '{rewardKey}' already claimed.");
662
+
663
+ progress.ClaimedRewards.Add(rewardKey);
664
+ allProgress[passId] = progress;
665
+ await SaveProgressAsync(context, client, allProgress);
666
+
667
+ return progress;
668
+ }
669
+
670
+ // ── PurchasePremium ──────────────────────────────────────────────────────
671
+
672
+ [CloudCodeFunction("PurchasePremium")]
673
+ public async Task<BattlePassProgress> PurchasePremium(
674
+ [CloudCodeService] IExecutionContext context, [CloudCodeService] IGameApiClient client, string passId)
675
+ {
676
+ var allProgress = await LoadProgressAsync(context, client);
677
+
678
+ if (!allProgress.TryGetValue(passId, out var progress))
679
+ progress = new BattlePassProgress { CurrentTier = 1, ClaimedRewards = new List<string>() };
680
+
681
+ if (progress.HasPremium)
682
+ return progress; // idempotent -- already owns premium
683
+
684
+ // Replace this throw with server-side purchase validation, then set HasPremium.
685
+ throw new Exception("PurchasePremium requires server-side purchase validation.");
686
+ }
687
+
688
+ // ── Helpers ──────────────────────────────────────────────────────────────
689
+
690
+ static void RequireActive(string passId)
691
+ {
692
+ if (!PassWindows.TryGetValue(passId, out var window))
693
+ throw new Exception($"Unknown pass '{passId}'.");
694
+
695
+ var now = DateTime.UtcNow;
696
+ if (now < window.Start || now >= window.End)
697
+ throw new Exception($"Pass '{passId}' is not active.");
698
+ }
699
+
700
+ static int CalculateTier(int currentXp, List<BattlePassTierThreshold> tiers)
701
+ {
702
+ var reached = tiers
703
+ .Where(t => currentXp >= t.XpRequired)
704
+ .OrderByDescending(t => t.TierNumber)
705
+ .FirstOrDefault();
706
+
707
+ return reached?.TierNumber ?? 1;
708
+ }
709
+
710
+ async Task<Dictionary<string, BattlePassProgress>> LoadProgressAsync(
711
+ IExecutionContext context, IGameApiClient client)
712
+ {
713
+ var result = await client.CloudSaveData.GetProtectedItemsAsync(
714
+ context, context.ServiceToken, context.ProjectId,
715
+ context.PlayerId!, new List<string> { ProgressKey });
716
+
717
+ var value = result.Data?.Results?.FirstOrDefault()?.Value?.ToString();
718
+ if (string.IsNullOrEmpty(value))
719
+ return new Dictionary<string, BattlePassProgress>();
720
+
721
+ return JsonConvert.DeserializeObject<Dictionary<string, BattlePassProgress>>(value)
722
+ ?? new Dictionary<string, BattlePassProgress>();
723
+ }
724
+
725
+ async Task SaveProgressAsync(
726
+ IExecutionContext context, IGameApiClient client,
727
+ Dictionary<string, BattlePassProgress> progress)
728
+ {
729
+ var json = JsonConvert.SerializeObject(progress);
730
+ var body = new SetItemBody(ProgressKey, json);
731
+ await client.CloudSaveData.SetProtectedItemAsync(
732
+ context, context.ServiceToken, context.ProjectId, context.PlayerId!, body);
733
+ }
734
+ }
735
+ }
736
+ ```
737
+
738
+ ### Server-Side Models (inside BattlePassModule.cs)
739
+
740
+ The single definition of both types, in the module's `Cloud` namespace. Use public members with a public getter and setter, as the auto-properties below: the generator copies only those into the client stub of `BattlePassProgress`, as `{ get; set; }` properties, and `Newtonsoft.Json` serializes them for Cloud Save. Do not declare a second `BattlePassProgress` in client code.
741
+
742
+ ```csharp
743
+ namespace BattlePassModule.Cloud
744
+ {
745
+ public class BattlePassProgress
746
+ {
747
+ public int CurrentXp { get; set; }
748
+ public int CurrentTier { get; set; }
749
+ public bool HasPremium { get; set; }
750
+ public List<string> ClaimedRewards { get; set; } = new();
751
+ }
752
+
753
+ public class BattlePassTierThreshold
754
+ {
755
+ public int TierNumber { get; set; }
756
+ public int XpRequired { get; set; }
757
+ }
758
+ }
759
+ ```
760
+
761
+ ---
762
+
763
+ ## Cloud Resources
764
+
765
+ ### Remote Config `.rc` File
766
+
767
+ Place at `Assets/BattlePasses.rc`. Contains pass definitions as a JSON array under the `"battle_passes"` key. Example with two concurrent passes:
768
+
769
+ ```json
770
+ {
771
+ "$schema": "https://ugs-config-schemas.unity3d.com/v1/remote-config.schema.json",
772
+ "entries": {
773
+ "battle_passes": [
774
+ {
775
+ "PassId": "season_03",
776
+ "Name": "Season 3",
777
+ "StartDate": "2024-01-01T00:00:00Z",
778
+ "EndDate": "2026-06-30T23:59:59Z",
779
+ "Tiers": [
780
+ { "TierNumber": 1, "XpRequired": 0, "FreeRewardId": "item_bronze_badge", "PremiumRewardId": "item_gold_frame" },
781
+ { "TierNumber": 2, "XpRequired": 500, "FreeRewardId": "item_xp_boost", "PremiumRewardId": "item_rare_skin_fragment" },
782
+ { "TierNumber": 3, "XpRequired": 1200, "FreeRewardId": "item_silver_badge", "PremiumRewardId": "item_legendary_skin" }
783
+ ]
784
+ },
785
+ {
786
+ "PassId": "easter_2026",
787
+ "Name": "Easter Event 2026",
788
+ "StartDate": "2026-04-01T00:00:00Z",
789
+ "EndDate": "2026-04-21T23:59:59Z",
790
+ "Tiers": [
791
+ { "TierNumber": 1, "XpRequired": 0, "FreeRewardId": "item_egg_basket", "PremiumRewardId": "item_golden_egg" },
792
+ { "TierNumber": 2, "XpRequired": 300, "FreeRewardId": "item_bunny_ears", "PremiumRewardId": "item_bunny_mount" }
793
+ ]
794
+ }
795
+ ]
796
+ },
797
+ "types": {
798
+ "battle_passes": "JSON"
799
+ }
800
+ }
801
+ ```
802
+
803
+ **Rules:**
804
+
805
+ - `PassId` must be unique across all passes and stable over time — it is the key used in player
806
+ progress records. Never rename a `PassId` after launch.
807
+ - `XpRequired` is **cumulative** XP to reach the tier, not a delta from the previous tier.
808
+ Tier 1 must always have `XpRequired: 0` so it is immediately reachable.
809
+ - `FreeRewardId` / `PremiumRewardId` are arbitrary strings. The client reads these IDs and maps
810
+ them to display names/icons locally. `ClaimReward` records the claim in `ClaimedRewards`; the game grants what
811
+ the ID stands for — unlock it client-side from the claimed set, or have the module grant it through
812
+ Cloud Save Protected or another Cloud Code call alongside the claim.
813
+ - Treat a claim as an entitlement only if the XP behind it is trustworthy. The sample `AwardXp`
814
+ accepts any `amount` up to the `MaxXpPerSource` cap from a signed-in client, so before
815
+ granting anything valuable, derive XP from server-verified events (or make `AwardXp`
816
+ non-client-callable and drive it from server-validated events).
817
+ - Make the grant idempotent and retryable from the saved claim: if the grant fails after the claim
818
+ is saved, a retry of `ClaimReward` fails its duplicate-claim check, so grant from the stored claim
819
+ (or persist claim and grant in one recoverable operation) rather than only on the first call.
820
+ - `ClaimedRewards` stores keys such as `tier_1_free`, not reward IDs. Keep reward IDs immutable
821
+ for the life of a pass, or store the granted reward ID with the claim, so a changed ID cannot
822
+ unlock a different reward for an old claim.
823
+ - Date filtering happens **client-side** after fetch. Add passes to the file before their
824
+ `StartDate` — they will be ignored by the client until the start time is reached.
825
+
826
+ ### Cloud Code Module
827
+
828
+ > Create the module files listed in [Cloud Code Module Implementation](#cloud-code-module-implementation) above.
829
+ > See the Creating a Cloud Code Module section of `references/cloud-code-modules.md`
830
+ > for the generic templates; replace `MyModule` with `BattlePassModule`.
831
+ >
832
+ > The `.ccmu` is deployed by the Deployment Window like any other cloud asset, but its code is compiled by
833
+ > Unity — a project that does not compile has nothing to deploy.
834
+
835
+ ### Access Control
836
+
837
+ **No Access Control (`.ac`) file is needed for the battle pass.** The Cloud Save Protected bucket is inherently server-only for writes -- only Cloud Code (using `IGameApiClient` with the service token) can write to it. Clients can read Protected data but cannot modify it.
838
+
839
+ > **Contrast with achievements:** The achievements skill offered a direct-write dev path using
840
+ > the Public bucket, which required an explicit Access Control policy to lock down in production.
841
+ > Battle pass has no direct-write path, so there is nothing to lock down.
842
+
843
+ ### `manifest.json` Entries
844
+
845
+ Ensure these packages are listed in `Packages/manifest.json`:
846
+
847
+ ```json
848
+ {
849
+ "dependencies": {
850
+ "com.unity.services.authentication": "3.6.1",
851
+ "com.unity.services.cloudcode": "3.0.0",
852
+ "com.unity.services.cloudsave": "3.4.0",
853
+ "com.unity.services.core": "1.16.0",
854
+ "com.unity.services.deployment": "1.7.2",
855
+ "com.unity.remote-config": "4.2.5",
856
+ "com.unity.services.tooling": "1.4.1",
857
+ "com.unity.nuget.newtonsoft-json": "3.2.2"
858
+ }
859
+ }
860
+ ```
861
+
862
+ > `com.unity.services.cloudcode` **`3.0.0`** is the native-authoring line, which the `.ccmu` module
863
+ > below requires — see the Requirements section of `references/cloud-code-modules.md`. The `2.10.x` line
864
+ > deploys legacy `.ccmr` modules only; use it when maintaining one, not for new work.
865
+
866
+
867
+ `com.unity.nuget.newtonsoft-json` is required for `JsonConvert` used in serialization of tier data and progress.
868
+
869
+ ---
870
+
871
+ ## Deployment Checklist
872
+
873
+ - [ ] All packages added to `Packages/manifest.json`
874
+ - [ ] Remote Config `.rc` file created with `"battle_passes"` entry and at least one active pass
875
+ - [ ] Cloud Code module authored as a `.ccmu`: module asset, `Cloud/` + `Client/` asmdefs, endpoint class, `CloudSetup.cs` (see file list in [Cloud Code Module Implementation](#cloud-code-module-implementation))
876
+ - [ ] `BattlePassModule.cs` added to the module project with `AwardXp`, `ClaimReward`, `PurchasePremium` functions
877
+ - [ ] Environment selected in **Services > Deployment** settings
878
+ - [ ] Deploy all via the Deployment Window: select `BattlePasses.rc` and `BattlePassModule.ccmu`, click **Deploy**
879
+ - [ ] Project linked in **Edit > Project Settings > Services**
880
+
881
+ ---
882
+
883
+ ## Validation
884
+
885
+ After implementing the battle pass feature, verify:
886
+
887
+ 1. **Project compiles** without errors — the module's Editor-only cloud assembly is compiled with the rest of the Unity project.
888
+ 2. **Initialization order** is correct: `UnityServices.InitializeAsync()` then `AuthenticationService.Instance.SignInAnonymouslyAsync()` then service calls.
889
+ 3. **Protected bucket reads** use `ProtectedReadAccessClassOptions` -- using the wrong access class returns no data.
890
+ 4. **Client bindings resolve**: the game assembly references the module's client assembly and calls `BattlePassModuleClient`. The deployed module name is the **cloud asmdef name**, `BattlePassModule` (the `name` field of `Cloud/BattlePassModule.asmdef`), not the `.ccmu` filename.
891
+ 5. **One progress definition**: `BattlePassProgress` is declared only in the Cloud Code module, as `{ get; set; }` properties, and the client uses its generated stub.
892
+ 6. **No direct client writes** to `"battle_pass_progress"` -- all mutations go through Cloud Code.
893
+ 7. **No client-supplied tier data**: `AwardXp` and `ClaimReward` use the module's server-side `PassTiers`, which matches the `XpRequired` values in `BattlePasses.rc`, and reject passes outside the server-side `PassWindows` (matching `StartDate`/`EndDate`).
894
+ 8. **Date filtering** uses `DateTime.UtcNow` with `DateTimeStyles.RoundtripKind` for consistent UTC parsing.
895
+ 9. **Cloud resources** are present and deployable: `BattlePasses.rc` and `BattlePassModule.ccmu` appear in the Deployment Window.
896
+ 10. **PurchasePremium is an integration stub** -- it returns existing progress when `HasPremium` is already true and otherwise throws until you add purchase validation.
897
+ 11. **ClaimReward validates** the pass window (`PassWindows`), tier reach (against `PassTiers`) and premium ownership before recording the claim.
898
+ 12. **Premium purchasing is not deployable until a validated purchase flow sets `HasPremium`** -- keep the fail-closed throw until then.
899
+
900
+ ---
901
+
902
+ ## Asset Store & Sample Projects
903
+
904
+ - **Use Case Samples — Battle Pass:** The official [Use Case Samples](https://github.com/Unity-Technologies/com.unity.services.samples.use-cases) repository includes a complete Battle Pass sample with seasonal reward tiers, free/premium tracks, time-limited rewards, and server-authoritative progression via Cloud Code. Clone the repo and open the "Battle Pass" scene for a working reference.