modwright 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 (455) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +212 -0
  3. package/assets/claude/agents/mw-explore.md +46 -0
  4. package/assets/claude/agents/mw-fixture.md +33 -0
  5. package/assets/claude/agents/mw-rule-author.md +51 -0
  6. package/assets/claude/agents/mw-scaffold-author.md +48 -0
  7. package/assets/claude/skills/mw-author.md +59 -0
  8. package/assets/claude/skills/mw-new-item.md +51 -0
  9. package/assets/claude/skills/mw-new-spell.md +50 -0
  10. package/assets/claude/skills/mw-scaffold.md +59 -0
  11. package/assets/claude/skills/mw-ship.md +57 -0
  12. package/assets/claude/skills/mw-validate.md +51 -0
  13. package/assets/claude/skills/mw-verify.md +59 -0
  14. package/assets/nivalisnights/extract_resources.py +177 -0
  15. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
  16. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
  17. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
  18. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
  19. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
  20. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
  21. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
  22. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
  23. package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
  24. package/bridges/bg3-se/README.md +99 -0
  25. package/bridges/bg3-se/bridge.json +68 -0
  26. package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
  27. package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
  28. package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
  29. package/bridges/cp2077-cet/README.md +165 -0
  30. package/bridges/cp2077-cet/bridge.json +53 -0
  31. package/dist/core/bridge/arm.js +175 -0
  32. package/dist/core/bridge/cet-console.js +26 -0
  33. package/dist/core/bridge/cet-log.js +362 -0
  34. package/dist/core/bridge/index.js +11 -0
  35. package/dist/core/bridge/locate.js +35 -0
  36. package/dist/core/bridge/paths.js +34 -0
  37. package/dist/core/bridge/pending.js +89 -0
  38. package/dist/core/bridge/poll.js +155 -0
  39. package/dist/core/bridge/stage.js +100 -0
  40. package/dist/core/bridge/status.js +97 -0
  41. package/dist/core/bridge/types.js +145 -0
  42. package/dist/core/bridge/validate.js +107 -0
  43. package/dist/core/build/index.js +6 -0
  44. package/dist/core/build/manifest.js +47 -0
  45. package/dist/core/build/paths.js +32 -0
  46. package/dist/core/build/run.js +342 -0
  47. package/dist/core/build/stage.js +265 -0
  48. package/dist/core/build/steps/bg3.js +129 -0
  49. package/dist/core/build/steps/core.js +195 -0
  50. package/dist/core/build/steps/cyberpunk.js +95 -0
  51. package/dist/core/build/steps/listing.js +35 -0
  52. package/dist/core/build/types.js +13 -0
  53. package/dist/core/build/zip.js +148 -0
  54. package/dist/core/compat/check.js +113 -0
  55. package/dist/core/compat/pe.js +220 -0
  56. package/dist/core/compat/types.js +1 -0
  57. package/dist/core/compat/version.js +70 -0
  58. package/dist/core/deploy/guards.js +292 -0
  59. package/dist/core/deploy/index.js +5 -0
  60. package/dist/core/deploy/plan.js +696 -0
  61. package/dist/core/deploy/reload-apply.js +361 -0
  62. package/dist/core/deploy/reload.js +358 -0
  63. package/dist/core/deploy/sources.js +169 -0
  64. package/dist/core/deploy/types.js +1 -0
  65. package/dist/core/epic.js +54 -0
  66. package/dist/core/fsutil.js +191 -0
  67. package/dist/core/home.js +70 -0
  68. package/dist/core/knowledge/facts.js +229 -0
  69. package/dist/core/knowledge/index.js +4 -0
  70. package/dist/core/knowledge/search.js +112 -0
  71. package/dist/core/knowledge/store.js +123 -0
  72. package/dist/core/knowledge/types.js +1 -0
  73. package/dist/core/ledger/check.js +42 -0
  74. package/dist/core/ledger/hooks.js +80 -0
  75. package/dist/core/ledger/index.js +7 -0
  76. package/dist/core/ledger/query.js +17 -0
  77. package/dist/core/ledger/record.js +84 -0
  78. package/dist/core/ledger/status.js +53 -0
  79. package/dist/core/ledger/store.js +98 -0
  80. package/dist/core/ledger/types.js +108 -0
  81. package/dist/core/logs/index.js +4 -0
  82. package/dist/core/logs/registry.js +50 -0
  83. package/dist/core/logs/scan.js +195 -0
  84. package/dist/core/logs/triage.js +160 -0
  85. package/dist/core/logs/types.js +1 -0
  86. package/dist/core/lsx.js +10 -0
  87. package/dist/core/lz4.js +66 -0
  88. package/dist/core/mods.js +229 -0
  89. package/dist/core/process.js +49 -0
  90. package/dist/core/project/build-steps.js +83 -0
  91. package/dist/core/project/describe.js +74 -0
  92. package/dist/core/project/index.js +5 -0
  93. package/dist/core/project/load.js +146 -0
  94. package/dist/core/project/schema.js +264 -0
  95. package/dist/core/project/types.js +3 -0
  96. package/dist/core/prompts.js +72 -0
  97. package/dist/core/registry.js +35 -0
  98. package/dist/core/safety/backup.js +206 -0
  99. package/dist/core/safety/execute.js +188 -0
  100. package/dist/core/safety/index.js +5 -0
  101. package/dist/core/safety/plan.js +122 -0
  102. package/dist/core/safety/restore.js +172 -0
  103. package/dist/core/safety/types.js +1 -0
  104. package/dist/core/scaffold/bootstrap.js +7 -0
  105. package/dist/core/scaffold/catalog.js +28 -0
  106. package/dist/core/scaffold/claude-assets.js +271 -0
  107. package/dist/core/scaffold/collisions.js +64 -0
  108. package/dist/core/scaffold/emit.js +356 -0
  109. package/dist/core/scaffold/engine.js +680 -0
  110. package/dist/core/scaffold/ids.js +78 -0
  111. package/dist/core/scaffold/index.js +10 -0
  112. package/dist/core/scaffold/jsonpath.js +121 -0
  113. package/dist/core/scaffold/load.js +81 -0
  114. package/dist/core/scaffold/render.js +85 -0
  115. package/dist/core/scaffold/sequence.js +396 -0
  116. package/dist/core/scaffold/types.js +309 -0
  117. package/dist/core/steam.js +63 -0
  118. package/dist/core/surfaceutil.js +36 -0
  119. package/dist/core/testplan/check.js +67 -0
  120. package/dist/core/testplan/index.js +5 -0
  121. package/dist/core/testplan/load.js +100 -0
  122. package/dist/core/testplan/registry.js +317 -0
  123. package/dist/core/testplan/run.js +389 -0
  124. package/dist/core/testplan/types.js +82 -0
  125. package/dist/core/text/converters/bg3.js +50 -0
  126. package/dist/core/text/converters/cyberpunk.js +36 -0
  127. package/dist/core/text/converters/index.js +261 -0
  128. package/dist/core/text/diff/cr2w.js +180 -0
  129. package/dist/core/text/diff/index.js +16 -0
  130. package/dist/core/text/diff/loca.js +43 -0
  131. package/dist/core/text/diff/lsx.js +126 -0
  132. package/dist/core/text/handles.js +86 -0
  133. package/dist/core/text/index.js +6 -0
  134. package/dist/core/text/json.js +46 -0
  135. package/dist/core/text/templates/cache.js +71 -0
  136. package/dist/core/text/templates/index.js +3 -0
  137. package/dist/core/text/templates/library.js +245 -0
  138. package/dist/core/text/templates/types.js +51 -0
  139. package/dist/core/text/toolchain.js +16 -0
  140. package/dist/core/text/types.js +1 -0
  141. package/dist/core/text/validate.js +149 -0
  142. package/dist/core/text/xml.js +58 -0
  143. package/dist/core/toolchain/datafetch.js +151 -0
  144. package/dist/core/toolchain/env.js +63 -0
  145. package/dist/core/toolchain/features.js +170 -0
  146. package/dist/core/toolchain/index.js +8 -0
  147. package/dist/core/toolchain/install.js +191 -0
  148. package/dist/core/toolchain/locate.js +275 -0
  149. package/dist/core/toolchain/remember.js +84 -0
  150. package/dist/core/toolchain/run.js +318 -0
  151. package/dist/core/toolchain/specs.js +351 -0
  152. package/dist/core/toolchain/types.js +1 -0
  153. package/dist/core/types.js +1 -0
  154. package/dist/core/userconfig.js +66 -0
  155. package/dist/core/validate/context.js +139 -0
  156. package/dist/core/validate/index.js +5 -0
  157. package/dist/core/validate/registry.js +58 -0
  158. package/dist/core/validate/run.js +131 -0
  159. package/dist/core/validate/suppressions.js +91 -0
  160. package/dist/core/validate/types.js +2 -0
  161. package/dist/core/yamledit.js +51 -0
  162. package/dist/index.js +389 -0
  163. package/dist/server.js +1914 -0
  164. package/dist/surfaces/baldursgate3/compat.js +164 -0
  165. package/dist/surfaces/baldursgate3/identity.js +110 -0
  166. package/dist/surfaces/baldursgate3/index/build.js +331 -0
  167. package/dist/surfaces/baldursgate3/index/index.js +17 -0
  168. package/dist/surfaces/baldursgate3/index/info.js +40 -0
  169. package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
  170. package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
  171. package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
  172. package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
  173. package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
  174. package/dist/surfaces/baldursgate3/index/queries.js +327 -0
  175. package/dist/surfaces/baldursgate3/index/schema.js +62 -0
  176. package/dist/surfaces/baldursgate3/index.js +3 -0
  177. package/dist/surfaces/baldursgate3/logs.js +278 -0
  178. package/dist/surfaces/baldursgate3/modsettings.js +115 -0
  179. package/dist/surfaces/baldursgate3/pak.js +153 -0
  180. package/dist/surfaces/baldursgate3/settings.js +49 -0
  181. package/dist/surfaces/baldursgate3/surface.js +232 -0
  182. package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
  183. package/dist/surfaces/baldursgate3/validators/files.js +476 -0
  184. package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
  185. package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
  186. package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
  187. package/dist/surfaces/baldursgate3/validators/story.js +790 -0
  188. package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
  189. package/dist/surfaces/cyberpunk2077/compat.js +223 -0
  190. package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
  191. package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
  192. package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
  193. package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
  194. package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
  195. package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
  196. package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
  197. package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
  198. package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
  199. package/dist/surfaces/cyberpunk2077/index.js +3 -0
  200. package/dist/surfaces/cyberpunk2077/logs.js +742 -0
  201. package/dist/surfaces/cyberpunk2077/paths.js +12 -0
  202. package/dist/surfaces/cyberpunk2077/surface.js +218 -0
  203. package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
  204. package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
  205. package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
  206. package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
  207. package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
  208. package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
  209. package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
  210. package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
  211. package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
  212. package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
  213. package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
  214. package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
  215. package/dist/surfaces/eldenring/binders.js +111 -0
  216. package/dist/surfaces/eldenring/compat.js +166 -0
  217. package/dist/surfaces/eldenring/dcx.js +49 -0
  218. package/dist/surfaces/eldenring/index.js +13 -0
  219. package/dist/surfaces/eldenring/loaders.js +132 -0
  220. package/dist/surfaces/eldenring/logs.js +67 -0
  221. package/dist/surfaces/eldenring/paramdef.js +256 -0
  222. package/dist/surfaces/eldenring/params.js +129 -0
  223. package/dist/surfaces/eldenring/surface.js +121 -0
  224. package/dist/surfaces/eldenring/tools.js +52 -0
  225. package/dist/surfaces/eldenring/validators/binders.js +193 -0
  226. package/dist/surfaces/eldenring/validators/packages.js +159 -0
  227. package/dist/surfaces/eldenring/validators/params.js +334 -0
  228. package/dist/surfaces/eldenring/validators/profiles.js +287 -0
  229. package/dist/surfaces/index.js +34 -0
  230. package/dist/surfaces/nivalisnights/compat.js +229 -0
  231. package/dist/surfaces/nivalisnights/index/build.js +310 -0
  232. package/dist/surfaces/nivalisnights/index/index.js +5 -0
  233. package/dist/surfaces/nivalisnights/index/queries.js +231 -0
  234. package/dist/surfaces/nivalisnights/index.js +4 -0
  235. package/dist/surfaces/nivalisnights/logs.js +473 -0
  236. package/dist/surfaces/nivalisnights/surface.js +195 -0
  237. package/dist/surfaces/skyrimse/archives.js +229 -0
  238. package/dist/surfaces/skyrimse/compat.js +163 -0
  239. package/dist/surfaces/skyrimse/index.js +12 -0
  240. package/dist/surfaces/skyrimse/loadorder.js +174 -0
  241. package/dist/surfaces/skyrimse/logs.js +241 -0
  242. package/dist/surfaces/skyrimse/plugins.js +149 -0
  243. package/dist/surfaces/skyrimse/records.js +94 -0
  244. package/dist/surfaces/skyrimse/surface.js +240 -0
  245. package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
  246. package/dist/surfaces/skyrimse/validators/archives.js +110 -0
  247. package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
  248. package/dist/surfaces/skyrimse/validators/records.js +117 -0
  249. package/dist/surfaces/stardewvalley/compat.js +190 -0
  250. package/dist/surfaces/stardewvalley/config.js +42 -0
  251. package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
  252. package/dist/surfaces/stardewvalley/index/index.js +229 -0
  253. package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
  254. package/dist/surfaces/stardewvalley/index.js +12 -0
  255. package/dist/surfaces/stardewvalley/logs.js +307 -0
  256. package/dist/surfaces/stardewvalley/mods.js +182 -0
  257. package/dist/surfaces/stardewvalley/surface.js +244 -0
  258. package/dist/surfaces/stardewvalley/validators/content.js +321 -0
  259. package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
  260. package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
  261. package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
  262. package/dist/surfaces/subnautica2/compat.js +138 -0
  263. package/dist/surfaces/subnautica2/index.js +5 -0
  264. package/dist/surfaces/subnautica2/logs.js +129 -0
  265. package/dist/surfaces/subnautica2/paks.js +173 -0
  266. package/dist/surfaces/subnautica2/surface.js +151 -0
  267. package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
  268. package/dist/surfaces/valheim/compat.js +171 -0
  269. package/dist/surfaces/valheim/index/index.js +301 -0
  270. package/dist/surfaces/valheim/index/tables.js +103 -0
  271. package/dist/surfaces/valheim/index.js +11 -0
  272. package/dist/surfaces/valheim/logs.js +197 -0
  273. package/dist/surfaces/valheim/packaging.js +131 -0
  274. package/dist/surfaces/valheim/plugins.js +270 -0
  275. package/dist/surfaces/valheim/references.js +121 -0
  276. package/dist/surfaces/valheim/surface.js +223 -0
  277. package/dist/surfaces/valheim/validators/packaging.js +218 -0
  278. package/dist/surfaces/valheim/validators/plugins.js +287 -0
  279. package/dist/surfaces/valheim/validators/references.js +169 -0
  280. package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
  281. package/knowledge/baldursgate3/audio.voice.yaml +182 -0
  282. package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
  283. package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
  284. package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
  285. package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
  286. package/knowledge/baldursgate3/crash.causes.yaml +90 -0
  287. package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
  288. package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
  289. package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
  290. package/knowledge/baldursgate3/dialogue.yaml +122 -0
  291. package/knowledge/baldursgate3/feats.classes.yaml +164 -0
  292. package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
  293. package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
  294. package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
  295. package/knowledge/baldursgate3/journal.quests.yaml +214 -0
  296. package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
  297. package/knowledge/baldursgate3/localization.handles.yaml +92 -0
  298. package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
  299. package/knowledge/baldursgate3/origins.companions.yaml +144 -0
  300. package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
  301. package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
  302. package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
  303. package/knowledge/baldursgate3/process.launch.yaml +34 -0
  304. package/knowledge/baldursgate3/progressions.yaml +92 -0
  305. package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
  306. package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
  307. package/knowledge/baldursgate3/races.progression.yaml +228 -0
  308. package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
  309. package/knowledge/baldursgate3/scriptextender.yaml +252 -0
  310. package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
  311. package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
  312. package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
  313. package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
  314. package/knowledge/baldursgate3/stats.spells.yaml +293 -0
  315. package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
  316. package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
  317. package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
  318. package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
  319. package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
  320. package/knowledge/baldursgate3/ui.icons.yaml +412 -0
  321. package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
  322. package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
  323. package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
  324. package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
  325. package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
  326. package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
  327. package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
  328. package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
  329. package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
  330. package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
  331. package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
  332. package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
  333. package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
  334. package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
  335. package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
  336. package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
  337. package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
  338. package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
  339. package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
  340. package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
  341. package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
  342. package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
  343. package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
  344. package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
  345. package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
  346. package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
  347. package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
  348. package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
  349. package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
  350. package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
  351. package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
  352. package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
  353. package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
  354. package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
  355. package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
  356. package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
  357. package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
  358. package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
  359. package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
  360. package/knowledge/cyberpunk2077/player.control.yaml +918 -0
  361. package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
  362. package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
  363. package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
  364. package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
  365. package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
  366. package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
  367. package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
  368. package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
  369. package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
  370. package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
  371. package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
  372. package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
  373. package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
  374. package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
  375. package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
  376. package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
  377. package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
  378. package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
  379. package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
  380. package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
  381. package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
  382. package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
  383. package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
  384. package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
  385. package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
  386. package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
  387. package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
  388. package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
  389. package/knowledge/eldenring/formats.binders.yaml +237 -0
  390. package/knowledge/eldenring/launcher.me3.yaml +230 -0
  391. package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
  392. package/knowledge/eldenring/params.paramdef.yaml +149 -0
  393. package/knowledge/eldenring/params.regulation.yaml +142 -0
  394. package/knowledge/eldenring/saves.online.yaml +118 -0
  395. package/knowledge/eldenring/toolchain.cli.yaml +256 -0
  396. package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
  397. package/knowledge/skyrimse/archives.bsa.yaml +212 -0
  398. package/knowledge/skyrimse/install.detection.yaml +41 -0
  399. package/knowledge/skyrimse/loadorder.files.yaml +242 -0
  400. package/knowledge/skyrimse/plugins.esl.yaml +189 -0
  401. package/knowledge/skyrimse/plugins.format.yaml +224 -0
  402. package/knowledge/skyrimse/skse.runtime.yaml +283 -0
  403. package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
  404. package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
  405. package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
  406. package/knowledge/stardewvalley/game.versions.yaml +202 -0
  407. package/knowledge/stardewvalley/install.layout.yaml +277 -0
  408. package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
  409. package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
  410. package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
  411. package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
  412. package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
  413. package/knowledge/subnautica2/game.build.yaml +331 -0
  414. package/knowledge/subnautica2/modding.state.yaml +210 -0
  415. package/knowledge/subnautica2/ue5.containers.yaml +72 -0
  416. package/knowledge/subnautica2/ue5.paks.yaml +203 -0
  417. package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
  418. package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
  419. package/knowledge/valheim/bepinex.loader.yaml +247 -0
  420. package/knowledge/valheim/bepinex.plugins.yaml +251 -0
  421. package/knowledge/valheim/game.versions.yaml +321 -0
  422. package/knowledge/valheim/install.layout.yaml +216 -0
  423. package/knowledge/valheim/jotunn.library.yaml +334 -0
  424. package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
  425. package/knowledge/valheim/toolchain.build.yaml +111 -0
  426. package/package.json +29 -0
  427. package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
  428. package/scaffolds/baldursgate3/new-item.yaml +264 -0
  429. package/scaffolds/baldursgate3/new-mod.yaml +174 -0
  430. package/scaffolds/baldursgate3/new-passive.yaml +100 -0
  431. package/scaffolds/baldursgate3/new-project.yaml +118 -0
  432. package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
  433. package/scaffolds/baldursgate3/new-spell.yaml +102 -0
  434. package/scaffolds/baldursgate3/new-status.yaml +119 -0
  435. package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
  436. package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
  437. package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
  438. package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
  439. package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
  440. package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
  441. package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
  442. package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
  443. package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
  444. package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
  445. package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
  446. package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
  447. package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
  448. package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
  449. package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
  450. package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
  451. package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
  452. package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
  453. package/scaffolds/stardewvalley/new-project.yaml +184 -0
  454. package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
  455. package/scripts/mw-tool.mjs +184 -0
@@ -0,0 +1,531 @@
1
+ import * as path from "node:path";
2
+ import { walk } from "../../../core/fsutil.js";
3
+ import { parseTweakXLYaml } from "../index/parsers/tweakxl-yaml.js";
4
+ import { CR2W_VALIDATORS, cnameValue, collectKey, existsInVanillaIndex, existsStaged, isJsonObject, readCr2wJson, readStagedCr2wJson, resourcePathValue, walkObjects, } from "./cr2w.js";
5
+ function fileTarget(t) {
6
+ return t.kind === "file" ? t.path : undefined;
7
+ }
8
+ function isEntJson(p) {
9
+ return p.toLowerCase().endsWith(".ent.json");
10
+ }
11
+ function isVehicleEntJson(p) {
12
+ return isEntJson(p) && /(^|[\\/])v_/i.test(p);
13
+ }
14
+ function isInkatlasJson(p) {
15
+ return /\.inkatlas\.json$/i.test(p);
16
+ }
17
+ function isYamlFile(p) {
18
+ return /\.ya?ml$/i.test(p);
19
+ }
20
+ function stagingRoot(project) {
21
+ return project.sources.find((s) => s.kind === "archive-staging")?.absPath;
22
+ }
23
+ function resolveDepotPath(root, depotPath) {
24
+ const stripped = depotPath.startsWith("*") ? depotPath.slice(1) : depotPath;
25
+ return path.join(root, ...stripped.split(/[\\/]/));
26
+ }
27
+ function isVanillaPath(depotPath) {
28
+ return /^base[\\/]/i.test(depotPath);
29
+ }
30
+ async function resolvesStagedOrVanilla(ctx, root, depotPath) {
31
+ if (isVanillaPath(depotPath)) {
32
+ if (!ctx.store)
33
+ return true;
34
+ return existsInVanillaIndex(ctx, depotPath);
35
+ }
36
+ return existsStaged(ctx, resolveDepotPath(root, depotPath));
37
+ }
38
+ async function allFilesUnder(project, predicate) {
39
+ const out = [];
40
+ for (const source of project.sources) {
41
+ if (!source.exists)
42
+ continue;
43
+ const walked = await walk(source.absPath, { limit: 20_000 });
44
+ for (const rel of walked.files) {
45
+ if (predicate(rel))
46
+ out.push(path.join(source.absPath, rel));
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ function entRootChunk(root) {
52
+ if (!isJsonObject(root))
53
+ return undefined;
54
+ const data = root.Data;
55
+ if (!isJsonObject(data))
56
+ return undefined;
57
+ const rc = data.RootChunk;
58
+ return isJsonObject(rc) ? rc : undefined;
59
+ }
60
+ function appDefinitionNames(root) {
61
+ const out = [];
62
+ walkObjects(root, (obj) => {
63
+ if (Array.isArray(obj.components) || obj.$type === "appearanceAppearanceDefinition") {
64
+ const n = cnameValue(obj.name);
65
+ if (n !== undefined)
66
+ out.push(n);
67
+ }
68
+ });
69
+ return out;
70
+ }
71
+ const ENT_APP_EVIDENCE = [
72
+ "cp2077.vehicles.entities.four-core-file-roles",
73
+ "cp2077.entities.entity-appearance-chain",
74
+ "cp2077.entities.appearance-submesh-references",
75
+ ];
76
+ export const VEHICLE_ENT_APP_RESOLUTION = {
77
+ id: "cp2077.vehicles.ent-app-resolution",
78
+ game: "cyberpunk2077",
79
+ groups: ["vehicles"],
80
+ modTypes: ["vehicle"],
81
+ summary: "A vehicle .ent's appearance entries name appearances present in its .app; the .app's component depotPaths and the .ent's rig resolve.",
82
+ appliesTo: (t) => t.kind === "project",
83
+ appliesToDescription: "once per validation, over every v_*.ent.json in the project",
84
+ needs: { project: true },
85
+ cost: "build",
86
+ severity: "block",
87
+ evidence: ENT_APP_EVIDENCE,
88
+ async run(ctx) {
89
+ const root = stagingRoot(ctx.project);
90
+ if (!root)
91
+ return [];
92
+ const findings = [];
93
+ const entFiles = await allFilesUnder(ctx.project, isVehicleEntJson);
94
+ for (const entFile of entFiles) {
95
+ const parsed = await readCr2wJson(ctx, entFile);
96
+ if (!parsed)
97
+ continue;
98
+ const rootChunk = entRootChunk(parsed.root);
99
+ if (!rootChunk)
100
+ continue;
101
+ const rig = resourcePathValue(rootChunk.rig ?? rootChunk.deformationRig);
102
+ if (rig && !(await resolvesStagedOrVanilla(ctx, root, rig))) {
103
+ findings.push({
104
+ validatorId: this.id,
105
+ severity: "block",
106
+ file: entFile,
107
+ message: isVanillaPath(rig)
108
+ ? `This vehicle's rig "${rig}" does not resolve in the vanilla archive index.`
109
+ : `This vehicle's rig "${rig}" is not staged.`,
110
+ fix: `Stage the rig at "${rig}", or fix the path.`,
111
+ evidence: ENT_APP_EVIDENCE,
112
+ });
113
+ }
114
+ const appearances = Array.isArray(rootChunk.appearances) ? rootChunk.appearances : [];
115
+ for (const entry of appearances) {
116
+ if (!isJsonObject(entry))
117
+ continue;
118
+ const localName = cnameValue(entry.appearanceName);
119
+ const appPath = resourcePathValue(entry.appearanceResource);
120
+ if (!appPath)
121
+ continue;
122
+ if (isVanillaPath(appPath)) {
123
+ if (ctx.store && !(await existsInVanillaIndex(ctx, appPath))) {
124
+ findings.push({
125
+ validatorId: this.id,
126
+ severity: "block",
127
+ file: entFile,
128
+ message: `Appearance entry "${localName ?? "?"}" points at .app "${appPath}", which does not resolve in the vanilla archive index.`,
129
+ fix: `Fix the path, or build the resource index (build_index) if it has not been built yet.`,
130
+ evidence: ENT_APP_EVIDENCE,
131
+ });
132
+ }
133
+ continue;
134
+ }
135
+ const appJson = await readStagedCr2wJson(ctx, resolveDepotPath(root, appPath));
136
+ if (!appJson) {
137
+ findings.push({
138
+ validatorId: this.id,
139
+ severity: "block",
140
+ file: entFile,
141
+ message: `Appearance entry "${localName ?? "?"}" points at .app "${appPath}", which is not staged.`,
142
+ fix: `Stage the .app at "${appPath}", or fix the path.`,
143
+ evidence: ENT_APP_EVIDENCE,
144
+ });
145
+ continue;
146
+ }
147
+ if (localName !== undefined && !appDefinitionNames(appJson.root).includes(localName)) {
148
+ findings.push({
149
+ validatorId: this.id,
150
+ severity: "block",
151
+ file: entFile,
152
+ related: [appJson.absPath],
153
+ message: `Appearance entry names "${localName}", but ${path.basename(appJson.absPath)} defines no appearance with that name.`,
154
+ fix: `Add an appearance named "${localName}" to ${path.basename(appJson.absPath)}, or fix the .ent's appearanceName.`,
155
+ evidence: ENT_APP_EVIDENCE,
156
+ });
157
+ }
158
+ for (const hit of collectKey(appJson.root, "DepotPath")) {
159
+ const p = resourcePathValue(hit.value);
160
+ if (!p || /^\d*$/.test(p))
161
+ continue;
162
+ if (isVanillaPath(p) && !ctx.store)
163
+ continue;
164
+ if (!(await resolvesStagedOrVanilla(ctx, root, p))) {
165
+ findings.push({
166
+ validatorId: this.id,
167
+ severity: "block",
168
+ file: appJson.absPath,
169
+ related: [entFile],
170
+ message: isVanillaPath(p)
171
+ ? `Component depot path "${p}" at ${hit.jsonPath} does not resolve in the vanilla archive index.`
172
+ : `Component depot path "${p}" at ${hit.jsonPath} is not staged.`,
173
+ fix: `Stage the file at "${p}", or fix the path.`,
174
+ evidence: ENT_APP_EVIDENCE,
175
+ });
176
+ }
177
+ }
178
+ }
179
+ }
180
+ return findings;
181
+ },
182
+ };
183
+ const APPEARANCE_NAME_EVIDENCE = [
184
+ "cp2077.sectors.node-appearance-name-full",
185
+ "cp2077.entities.entity-appearance-factory-resolution",
186
+ "cp2077.tweakdb.weapon-visuals-via-appearanceresourcename",
187
+ ];
188
+ function worldEntityNodes(root) {
189
+ const out = [];
190
+ walkObjects(root, (obj, jsonPath) => {
191
+ if (obj.$type !== "worldEntityNode")
192
+ return;
193
+ out.push({
194
+ appearanceName: cnameValue(obj.appearanceName),
195
+ entityTemplate: resourcePathValue(obj.entityTemplate),
196
+ jsonPath,
197
+ });
198
+ });
199
+ return out;
200
+ }
201
+ export const VEHICLE_APPEARANCE_NAME_RESOLUTION = {
202
+ id: "cp2077.vehicles.appearance-name-resolution",
203
+ game: "cyberpunk2077",
204
+ groups: ["vehicles"],
205
+ modTypes: ["vehicle", "world-sector"],
206
+ summary: "A sector node's appearanceName is the full internal appearance name present in the referenced .ent's appearances and its .app.",
207
+ appliesTo: (t) => t.kind === "project",
208
+ appliesToDescription: "once per validation, over every worldEntityNode in every staged sector json",
209
+ needs: { project: true },
210
+ cost: "build",
211
+ severity: "block",
212
+ evidence: APPEARANCE_NAME_EVIDENCE,
213
+ async run(ctx) {
214
+ const root = stagingRoot(ctx.project);
215
+ if (!root)
216
+ return [];
217
+ const findings = [];
218
+ const jsonFiles = await allFilesUnder(ctx.project, (p) => p.toLowerCase().endsWith(".json"));
219
+ for (const file of jsonFiles) {
220
+ const parsed = await readCr2wJson(ctx, file);
221
+ if (!parsed)
222
+ continue;
223
+ for (const node of worldEntityNodes(parsed.root)) {
224
+ if (!node.appearanceName || !node.entityTemplate)
225
+ continue;
226
+ if (isVanillaPath(node.entityTemplate)) {
227
+ if (ctx.store && !(await existsInVanillaIndex(ctx, node.entityTemplate))) {
228
+ findings.push({
229
+ validatorId: this.id,
230
+ severity: "block",
231
+ file,
232
+ message: `Sector node's entityTemplate "${node.entityTemplate}" does not resolve in the vanilla archive index.`,
233
+ fix: `Fix the path, or build the resource index (build_index) if it has not been built yet.`,
234
+ evidence: APPEARANCE_NAME_EVIDENCE,
235
+ });
236
+ }
237
+ continue;
238
+ }
239
+ const entJson = await readStagedCr2wJson(ctx, resolveDepotPath(root, node.entityTemplate));
240
+ if (!entJson)
241
+ continue;
242
+ const rootChunk = entRootChunk(entJson.root);
243
+ const appearances = rootChunk && Array.isArray(rootChunk.appearances) ? rootChunk.appearances : [];
244
+ const fullNames = appearances
245
+ .filter((e) => isJsonObject(e))
246
+ .map((e) => cnameValue(e.name))
247
+ .filter((n) => n !== undefined);
248
+ if (fullNames.length === 0)
249
+ continue;
250
+ if (!fullNames.includes(node.appearanceName)) {
251
+ findings.push({
252
+ validatorId: this.id,
253
+ severity: "block",
254
+ file,
255
+ related: [entJson.absPath],
256
+ message: `Sector node at ${node.jsonPath} sets appearanceName "${node.appearanceName}", which is not one of ` +
257
+ `${path.basename(entJson.absPath)}'s declared appearance names (${fullNames.join(", ")}). A wrong appearanceName ` +
258
+ "spawns an invisible entity with no log line.",
259
+ fix: `Use one of ${fullNames.join(", ")}, or add an appearance named "${node.appearanceName}" to the entity template.`,
260
+ evidence: APPEARANCE_NAME_EVIDENCE,
261
+ });
262
+ }
263
+ }
264
+ }
265
+ return findings;
266
+ },
267
+ };
268
+ const TWEAK_NAME_EVIDENCE = ["cp2077.vehicles.entities.tweak-name-from-ent-filename"];
269
+ function stripAppearanceSuffix(stem) {
270
+ const withWord = /^(.*)_{1,2}[a-z][a-z0-9]*_\d{2}$/i.exec(stem);
271
+ if (withWord)
272
+ return withWord[1].replace(/_+$/, "");
273
+ const bare = /^(.*)_{1,2}\d{2}$/.exec(stem);
274
+ return bare ? bare[1].replace(/_+$/, "") : stem;
275
+ }
276
+ export const VEHICLE_TWEAK_NAME_FROM_ENT = {
277
+ id: "cp2077.vehicles.tweak-name-from-ent",
278
+ game: "cyberpunk2077",
279
+ groups: ["vehicles"],
280
+ modTypes: ["vehicle"],
281
+ summary: "A vehicle's TweakDB record name matches the .ent filename with the appearance suffix stripped.",
282
+ appliesTo: (t) => {
283
+ const f = fileTarget(t);
284
+ return f !== undefined && isVehicleEntJson(f);
285
+ },
286
+ appliesToDescription: "every v_*.ent.json file, only when the project declares at least one Vehicle.* TweakDB record",
287
+ needs: { project: true },
288
+ cost: "edit",
289
+ severity: "warn",
290
+ evidence: TWEAK_NAME_EVIDENCE,
291
+ async run(ctx) {
292
+ const file = fileTarget(ctx.target);
293
+ if (!file)
294
+ return [];
295
+ const stem = path.basename(file).replace(/\.ent\.json$/i, "");
296
+ const expected = `Vehicle.${stripAppearanceSuffix(stem)}`;
297
+ const yamlFiles = await allFilesUnder(ctx.project, isYamlFile);
298
+ const vehicleRecordNames = new Set();
299
+ for (const yamlFile of yamlFiles) {
300
+ let text;
301
+ try {
302
+ text = await ctx.readText(yamlFile);
303
+ }
304
+ catch {
305
+ continue;
306
+ }
307
+ let records;
308
+ try {
309
+ records = parseTweakXLYaml(text, yamlFile);
310
+ }
311
+ catch {
312
+ continue;
313
+ }
314
+ for (const rec of records) {
315
+ if (rec.name.startsWith("Vehicle."))
316
+ vehicleRecordNames.add(rec.name);
317
+ }
318
+ }
319
+ if (vehicleRecordNames.size === 0)
320
+ return [];
321
+ if (vehicleRecordNames.has(expected))
322
+ return [];
323
+ return [
324
+ {
325
+ validatorId: this.id,
326
+ severity: "warn",
327
+ file,
328
+ message: `No TweakDB record named "${expected}" was found (derived from this .ent's filename with the appearance suffix stripped). ` +
329
+ `Declared Vehicle.* records: ${[...vehicleRecordNames].join(", ")}.`,
330
+ fix: `Name the vehicle's TweakDB record "${expected}", or confirm the filename really matches an existing record.`,
331
+ evidence: TWEAK_NAME_EVIDENCE,
332
+ },
333
+ ];
334
+ },
335
+ };
336
+ const INKATLAS_RANGE_EVIDENCE = [
337
+ "cp2077.vehicles.dashboard-ui.inkatlas-uv-percentage-confirms-existing",
338
+ "cp2077.vehicles.dashboard-ui.inkatlas-three-resolution-slots",
339
+ "cp2077.ui.icon-atlas-texture-resolution",
340
+ ];
341
+ export const VEHICLE_INKATLAS_UV_RANGE = {
342
+ id: "cp2077.vehicles.inkatlas-uv-range",
343
+ game: "cyberpunk2077",
344
+ groups: ["vehicles"],
345
+ modTypes: ["vehicle", "ui"],
346
+ summary: "Every inkTextureAtlasMapper slot's four rect coordinates are floats in [0,1] with top-left <= bottom-right; inkTextureSlot 0 is populated.",
347
+ appliesTo: (t) => {
348
+ const f = fileTarget(t);
349
+ return f !== undefined && isInkatlasJson(f);
350
+ },
351
+ appliesToDescription: "every *.inkatlas.json file",
352
+ needs: {},
353
+ cost: "edit",
354
+ severity: "block",
355
+ evidence: INKATLAS_RANGE_EVIDENCE,
356
+ async run(ctx) {
357
+ const file = fileTarget(ctx.target);
358
+ if (!file)
359
+ return [];
360
+ const parsed = await readCr2wJson(ctx, file);
361
+ if (!parsed)
362
+ return [];
363
+ const findings = [];
364
+ walkObjects(parsed.root, (obj, jsonPath) => {
365
+ if (!isJsonObject(obj.rect))
366
+ return;
367
+ const rect = obj.rect;
368
+ const top = rect.Top;
369
+ const left = rect.Left;
370
+ const bottom = rect.Bottom;
371
+ const right = rect.Right;
372
+ if (typeof top !== "number" || typeof left !== "number" || typeof bottom !== "number" || typeof right !== "number")
373
+ return;
374
+ const name = cnameValue(obj.name) ?? "(unnamed part)";
375
+ const outOfRange = [top, left, bottom, right].some((v) => v < 0 || v > 1);
376
+ if (outOfRange) {
377
+ findings.push({
378
+ validatorId: "cp2077.vehicles.inkatlas-uv-range",
379
+ severity: "block",
380
+ file,
381
+ message: `Part "${name}" at ${jsonPath} has a rect coordinate outside [0, 1] (Top=${top}, Left=${left}, Bottom=${bottom}, Right=${right}). Inkatlas coordinates are percentages, not pixels.`,
382
+ fix: "Express the rect as a fraction of the atlas texture (0 to 1), not a pixel coordinate.",
383
+ evidence: INKATLAS_RANGE_EVIDENCE,
384
+ });
385
+ }
386
+ else if (left > right || top > bottom) {
387
+ findings.push({
388
+ validatorId: "cp2077.vehicles.inkatlas-uv-range",
389
+ severity: "block",
390
+ file,
391
+ message: `Part "${name}" at ${jsonPath} has a reversed rect (Left=${left} > Right=${right}, or Top=${top} > Bottom=${bottom}). The top-left corner must be <= the bottom-right corner on both axes.`,
392
+ fix: "Swap the coordinates so Left <= Right and Top <= Bottom.",
393
+ evidence: INKATLAS_RANGE_EVIDENCE,
394
+ });
395
+ }
396
+ });
397
+ const slots = collectKey(parsed.root, "slots").find((h) => Array.isArray(h.value));
398
+ if (slots && Array.isArray(slots.value)) {
399
+ const slot0 = slots.value[0];
400
+ const texturePath = isJsonObject(slot0) ? resourcePathValue(slot0.texture) : undefined;
401
+ if (!texturePath || /^\d*$/.test(texturePath)) {
402
+ findings.push({
403
+ validatorId: "cp2077.vehicles.inkatlas-uv-range",
404
+ severity: "block",
405
+ file,
406
+ message: "inkTextureSlot 0 has no texture populated; slot 0 is the only one required for modding.",
407
+ fix: "Populate slots[0].texture with the atlas's source texture depot path.",
408
+ evidence: INKATLAS_RANGE_EVIDENCE,
409
+ });
410
+ }
411
+ }
412
+ return findings;
413
+ },
414
+ };
415
+ const INKATLAS_PART_EVIDENCE = [
416
+ "cp2077.vehicles.dashboard-ui.atlaspartname-mismatch-is-silent",
417
+ "cp2077.vehicles.dashboard-ui.imagewidget-two-part-atlas-reference",
418
+ "cp2077.ui.icon-atlas-part-declaration",
419
+ "cp2077.ui.icon-path-record-name",
420
+ ];
421
+ function partNames(atlasRoot) {
422
+ return collectKey(atlasRoot, "parts")
423
+ .flatMap((h) => (Array.isArray(h.value) ? h.value : []))
424
+ .map((p) => (isJsonObject(p) ? cnameValue(p.name) : undefined))
425
+ .filter((n) => n !== undefined);
426
+ }
427
+ function widgetAtlasRefs(root, file) {
428
+ const out = [];
429
+ walkObjects(root, (obj, jsonPath) => {
430
+ if (!("texturePart" in obj) || !("textureAtlas" in obj))
431
+ return;
432
+ const atlasPath = resourcePathValue(obj.textureAtlas);
433
+ const partName = cnameValue(obj.texturePart);
434
+ if (atlasPath && partName)
435
+ out.push({ file, atlasPath, partName, jsonPath });
436
+ });
437
+ return out;
438
+ }
439
+ export const VEHICLE_INKATLAS_PART_RESOLVES = {
440
+ id: "cp2077.vehicles.inkatlas-part-resolves",
441
+ game: "cyberpunk2077",
442
+ groups: ["vehicles"],
443
+ modTypes: ["vehicle", "ui", "icon"],
444
+ summary: "Every texturePart on an inkImageWidget, and every atlasPartName on a UIIcon record, matches a partName in the referenced .inkatlas.",
445
+ appliesTo: (t) => t.kind === "project",
446
+ appliesToDescription: "once per validation, over every inkImageWidget textureAtlas/texturePart pair and UIIcon atlasResourcePath/atlasPartName pair",
447
+ needs: { project: true },
448
+ cost: "build",
449
+ severity: "block",
450
+ evidence: INKATLAS_PART_EVIDENCE,
451
+ async run(ctx) {
452
+ const root = stagingRoot(ctx.project);
453
+ if (!root)
454
+ return [];
455
+ const findings = [];
456
+ const refs = [];
457
+ for (const file of await allFilesUnder(ctx.project, (p) => p.toLowerCase().endsWith(".json"))) {
458
+ const parsed = await readCr2wJson(ctx, file);
459
+ if (!parsed)
460
+ continue;
461
+ refs.push(...widgetAtlasRefs(parsed.root, file));
462
+ }
463
+ for (const yamlFile of await allFilesUnder(ctx.project, isYamlFile)) {
464
+ let text;
465
+ try {
466
+ text = await ctx.readText(yamlFile);
467
+ }
468
+ catch {
469
+ continue;
470
+ }
471
+ let records;
472
+ try {
473
+ records = parseTweakXLYaml(text, yamlFile);
474
+ }
475
+ catch {
476
+ continue;
477
+ }
478
+ for (const rec of records) {
479
+ const atlasFlat = rec.flats.find((f) => f.name === "atlasResourcePath");
480
+ const partFlat = rec.flats.find((f) => f.name === "atlasPartName");
481
+ if (atlasFlat && partFlat && typeof atlasFlat.value === "string" && typeof partFlat.value === "string") {
482
+ refs.push({ file: yamlFile, atlasPath: atlasFlat.value, partName: partFlat.value, jsonPath: rec.name });
483
+ }
484
+ }
485
+ }
486
+ const atlasCache = new Map();
487
+ for (const ref of refs) {
488
+ if (isVanillaPath(ref.atlasPath)) {
489
+ if (ctx.store && !(await existsInVanillaIndex(ctx, ref.atlasPath))) {
490
+ findings.push({
491
+ validatorId: this.id,
492
+ severity: "block",
493
+ file: ref.file,
494
+ message: `"${ref.atlasPath}" at ${ref.jsonPath} does not resolve in the vanilla archive index.`,
495
+ fix: `Fix the path, or build the resource index (build_index) if it has not been built yet.`,
496
+ evidence: INKATLAS_PART_EVIDENCE,
497
+ });
498
+ }
499
+ continue;
500
+ }
501
+ let parts = atlasCache.get(ref.atlasPath);
502
+ if (parts === undefined && !atlasCache.has(ref.atlasPath)) {
503
+ const atlasJson = await readStagedCr2wJson(ctx, resolveDepotPath(root, ref.atlasPath));
504
+ parts = atlasJson ? partNames(atlasJson.root) : undefined;
505
+ atlasCache.set(ref.atlasPath, parts);
506
+ }
507
+ if (parts === undefined)
508
+ continue;
509
+ if (!parts.includes(ref.partName)) {
510
+ findings.push({
511
+ validatorId: this.id,
512
+ severity: "block",
513
+ file: ref.file,
514
+ message: `"${ref.partName}" at ${ref.jsonPath} has no matching part in "${ref.atlasPath}"'s inkTextureAtlasMapper (declared parts: ${parts.join(", ") || "(none)"}). An empty icon is displayed for a mismatch like this.`,
515
+ fix: `Add a part named "${ref.partName}" to the atlas, or fix the name to match one already declared.`,
516
+ evidence: INKATLAS_PART_EVIDENCE,
517
+ });
518
+ }
519
+ }
520
+ return findings;
521
+ },
522
+ };
523
+ export const VEHICLE_VALIDATORS = [
524
+ VEHICLE_ENT_APP_RESOLUTION,
525
+ VEHICLE_APPEARANCE_NAME_RESOLUTION,
526
+ VEHICLE_TWEAK_NAME_FROM_ENT,
527
+ VEHICLE_INKATLAS_UV_RANGE,
528
+ VEHICLE_INKATLAS_PART_RESOLVES,
529
+ ...CR2W_VALIDATORS,
530
+ ];
531
+ export { CR2W_VALIDATORS } from "./cr2w.js";