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,338 @@
1
+ const ID = "cp2077.history.mesh-preexport";
2
+ const GAME = "cyberpunk2077";
3
+ const GROUPS = ["history"];
4
+ const MOD_TYPES = ["mesh", "garment", "weapon", "vehicle"];
5
+ const VERTEX_CAP = 65535;
6
+ const ICOSPHERE_VERTEX_SIGNATURE = 42;
7
+ const UV_LAYER_NAME = "UVMap";
8
+ const DEGENERATE_AREA_EPSILON = 1e-9;
9
+ const EVIDENCE = [
10
+ "cp2077.mesh.vertex-cap-65535",
11
+ "cp2077.toolchain.glb-import-ignores-transforms",
12
+ "cp2077.toolchain.glb-contains-junk-icosphere",
13
+ "cp2077.mesh.glb-triangulation-required",
14
+ "cp2077.mesh.glb-tangent-vectors-required",
15
+ "cp2077.mesh.glb-uv-layers-required",
16
+ ];
17
+ const IDENTITY_MATRIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
18
+ function approxEqual(a, b, eps = 1e-6) {
19
+ return Math.abs(a - b) <= eps;
20
+ }
21
+ function vecApproxEqual(v, expected, eps = 1e-6) {
22
+ if (!v)
23
+ return true;
24
+ if (v.length !== expected.length)
25
+ return false;
26
+ return v.every((n, i) => approxEqual(n, expected[i] ?? 0, eps));
27
+ }
28
+ function nodeIsIdentity(node) {
29
+ if (node.matrix)
30
+ return vecApproxEqual(node.matrix, IDENTITY_MATRIX);
31
+ return (vecApproxEqual(node.translation, [0, 0, 0]) &&
32
+ vecApproxEqual(node.rotation, [0, 0, 0, 1]) &&
33
+ vecApproxEqual(node.scale, [1, 1, 1]));
34
+ }
35
+ function decodeBase64DataUri(uri) {
36
+ if (!uri)
37
+ return undefined;
38
+ const match = /^data:[^,]*;base64,(.*)$/is.exec(uri);
39
+ const encoded = match?.[1];
40
+ if (encoded === undefined)
41
+ return undefined;
42
+ try {
43
+ return Buffer.from(encoded, "base64");
44
+ }
45
+ catch {
46
+ return undefined;
47
+ }
48
+ }
49
+ function componentByteSize(componentType) {
50
+ switch (componentType) {
51
+ case 5120:
52
+ case 5121:
53
+ return 1;
54
+ case 5122:
55
+ case 5123:
56
+ return 2;
57
+ case 5125:
58
+ case 5126:
59
+ return 4;
60
+ default:
61
+ return 4;
62
+ }
63
+ }
64
+ function numComponents(type) {
65
+ switch (type) {
66
+ case "SCALAR":
67
+ return 1;
68
+ case "VEC2":
69
+ return 2;
70
+ case "VEC3":
71
+ return 3;
72
+ case "VEC4":
73
+ return 4;
74
+ default:
75
+ return 1;
76
+ }
77
+ }
78
+ function readAccessorBytes(doc, accessorIndex) {
79
+ if (accessorIndex === undefined)
80
+ return undefined;
81
+ const accessor = doc.accessors?.[accessorIndex];
82
+ if (!accessor || accessor.bufferView === undefined)
83
+ return undefined;
84
+ const view = doc.bufferViews?.[accessor.bufferView];
85
+ if (!view)
86
+ return undefined;
87
+ const buffer = doc.buffers?.[view.buffer];
88
+ const bytes = decodeBase64DataUri(buffer?.uri);
89
+ if (!bytes)
90
+ return undefined;
91
+ const componentType = accessor.componentType ?? 5126;
92
+ const start = (view.byteOffset ?? 0) + (accessor.byteOffset ?? 0);
93
+ const length = accessor.count * numComponents(accessor.type) * componentByteSize(componentType);
94
+ if (start < 0 || start + length > bytes.length)
95
+ return undefined;
96
+ return { data: bytes.subarray(start, start + length), componentType, count: accessor.count };
97
+ }
98
+ function readIndexArray(raw) {
99
+ const dv = new DataView(raw.data.buffer, raw.data.byteOffset, raw.data.byteLength);
100
+ const out = [];
101
+ for (let i = 0; i < raw.count; i++) {
102
+ switch (raw.componentType) {
103
+ case 5121:
104
+ out.push(dv.getUint8(i));
105
+ break;
106
+ case 5123:
107
+ out.push(dv.getUint16(i * 2, true));
108
+ break;
109
+ case 5125:
110
+ out.push(dv.getUint32(i * 4, true));
111
+ break;
112
+ default:
113
+ return undefined;
114
+ }
115
+ }
116
+ return out;
117
+ }
118
+ function readVec3Array(raw) {
119
+ if (raw.componentType !== 5126)
120
+ return undefined;
121
+ const dv = new DataView(raw.data.buffer, raw.data.byteOffset, raw.data.byteLength);
122
+ const out = [];
123
+ for (let i = 0; i < raw.count; i++) {
124
+ out.push([dv.getFloat32(i * 12, true), dv.getFloat32(i * 12 + 4, true), dv.getFloat32(i * 12 + 8, true)]);
125
+ }
126
+ return out;
127
+ }
128
+ function triangleArea(a, b, c) {
129
+ const ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
130
+ const ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
131
+ const cross = [
132
+ ab[1] * ac[2] - ab[2] * ac[1],
133
+ ab[2] * ac[0] - ab[0] * ac[2],
134
+ ab[0] * ac[1] - ab[1] * ac[0],
135
+ ];
136
+ return 0.5 * Math.sqrt(cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]);
137
+ }
138
+ function meshLabel(doc, meshIndex, primitiveIndex) {
139
+ const mesh = doc.meshes?.[meshIndex];
140
+ const name = mesh?.name ? `"${mesh.name}"` : `#${meshIndex}`;
141
+ return `mesh ${name} primitive ${primitiveIndex}`;
142
+ }
143
+ function checkGltfDoc(doc, file) {
144
+ const findings = [];
145
+ const meshes = doc.meshes ?? [];
146
+ for (const node of doc.nodes ?? []) {
147
+ if (node.mesh === undefined)
148
+ continue;
149
+ if (!nodeIsIdentity(node)) {
150
+ findings.push({
151
+ validatorId: ID,
152
+ severity: "block",
153
+ file,
154
+ message: `Node ${node.name ? `"${node.name}"` : `referencing mesh #${node.mesh}`} carries an un-applied transform (translation/rotation/scale/matrix is not identity). WolvenKit's GLB import ignores node transforms — run transform_apply(location=True, rotation=True, scale=True) in Blender before export, or the imported submesh lands in the wrong place relative to the donor mesh.`,
155
+ fix: "In Blender: select the object, Object > Apply > All Transforms (or bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)) before exporting the GLB.",
156
+ evidence: ["cp2077.toolchain.glb-import-ignores-transforms"],
157
+ });
158
+ }
159
+ if (node.name && /icosphere/i.test(node.name)) {
160
+ findings.push({
161
+ validatorId: ID,
162
+ severity: "block",
163
+ file,
164
+ message: `Node "${node.name}" looks like the junk 2 m icosphere every WolvenKit GLB export carries. Delete it in Blender before re-exporting — it must not reach the game mesh.`,
165
+ fix: 'Delete every object whose name contains "Icosphere" before export (an export script can drop them by name before writing the GLB).',
166
+ evidence: ["cp2077.toolchain.glb-contains-junk-icosphere"],
167
+ });
168
+ }
169
+ }
170
+ meshes.forEach((mesh, meshIndex) => {
171
+ mesh.primitives.forEach((primitive, primitiveIndex) => {
172
+ const label = meshLabel(doc, meshIndex, primitiveIndex);
173
+ const positionAccessorIndex = primitive.attributes.POSITION;
174
+ const positionAccessor = positionAccessorIndex !== undefined ? doc.accessors?.[positionAccessorIndex] : undefined;
175
+ if (positionAccessor) {
176
+ if (positionAccessor.count > VERTEX_CAP) {
177
+ findings.push({
178
+ validatorId: ID,
179
+ severity: "block",
180
+ file,
181
+ message: `${label} has ${positionAccessor.count} vertices, over the engine's hard cap of ${VERTEX_CAP} per submesh. The mesh will render incompletely or fail to load silently. Split the submesh or de-duplicate double-sided geometry instead of doubling it wholesale (doubling a whole weapon mesh for a double-sided material is how this cap was first hit).`,
182
+ fix: "Do not add more submeshes to dodge the cap — duplicate and normal-flip only the minimum geometry that needs a back face, and repair inward-facing normals at the source in Blender.",
183
+ evidence: ["cp2077.mesh.vertex-cap-65535"],
184
+ });
185
+ }
186
+ if (positionAccessor.count === ICOSPHERE_VERTEX_SIGNATURE &&
187
+ !(mesh.name && /icosphere/i.test(mesh.name))) {
188
+ findings.push({
189
+ validatorId: ID,
190
+ severity: "block",
191
+ file,
192
+ message: `${label} has exactly ${ICOSPHERE_VERTEX_SIGNATURE} vertices — the signature of WolvenKit's junk 2 m icosphere (unnamed or renamed so the "Icosphere" name check missed it). Delete it in Blender before re-exporting.`,
193
+ fix: 'Delete the object; if it survived a rename, check by vertex count (42) rather than by name alone.',
194
+ evidence: ["cp2077.toolchain.glb-contains-junk-icosphere"],
195
+ });
196
+ }
197
+ }
198
+ const uvAttributes = Object.keys(primitive.attributes).filter((key) => key.startsWith("TEXCOORD_"));
199
+ if (uvAttributes.length === 0) {
200
+ findings.push({
201
+ validatorId: ID,
202
+ severity: "warn",
203
+ file,
204
+ message: `${label} has no UV layer. WolvenKit GLB import needs a UV set; Blender's export step smart-projects a fresh one when none exists, but exporting without one leaves it to chance.`,
205
+ fix: `In Blender, add one UV layer named exactly "${UV_LAYER_NAME}" (smart UV project, angle_limit=1.15, island_margin=0.02, is the project's convention).`,
206
+ evidence: ["cp2077.mesh.glb-uv-layers-required"],
207
+ });
208
+ }
209
+ else {
210
+ if (uvAttributes.length > 1) {
211
+ findings.push({
212
+ validatorId: ID,
213
+ severity: "warn",
214
+ file,
215
+ message: `${label} carries ${uvAttributes.length} UV layers (${uvAttributes.sort().join(", ")}). Export exactly one, named "${UV_LAYER_NAME}" — extra layers are removed by the project's own export step.`,
216
+ fix: `Remove every UV layer except the one named "${UV_LAYER_NAME}" before export.`,
217
+ evidence: ["cp2077.mesh.glb-uv-layers-required"],
218
+ });
219
+ }
220
+ const texcoord0Index = primitive.attributes["TEXCOORD_0"];
221
+ const uv0Accessor = texcoord0Index !== undefined ? doc.accessors?.[texcoord0Index] : undefined;
222
+ if (uv0Accessor?.name !== undefined && uv0Accessor.name !== UV_LAYER_NAME) {
223
+ findings.push({
224
+ validatorId: ID,
225
+ severity: "warn",
226
+ file,
227
+ message: `${label}'s UV layer is named "${uv0Accessor.name}"; the project's convention requires exactly "${UV_LAYER_NAME}" (capital U, V, M).`,
228
+ fix: `Rename the UV layer to "${UV_LAYER_NAME}" in Blender before export.`,
229
+ evidence: ["cp2077.mesh.glb-uv-layers-required"],
230
+ });
231
+ }
232
+ }
233
+ if (primitive.indices !== undefined && positionAccessorIndex !== undefined && (primitive.mode ?? 4) === 4) {
234
+ const indexBytes = readAccessorBytes(doc, primitive.indices);
235
+ const positionBytes = readAccessorBytes(doc, positionAccessorIndex);
236
+ const indices = indexBytes && readIndexArray(indexBytes);
237
+ const positions = positionBytes && readVec3Array(positionBytes);
238
+ if (indices && positions && indices.length % 3 === 0) {
239
+ for (let t = 0; t < indices.length; t += 3) {
240
+ const ia = indices[t];
241
+ const ib = indices[t + 1];
242
+ const ic = indices[t + 2];
243
+ if (ia === undefined || ib === undefined || ic === undefined)
244
+ continue;
245
+ const pa = positions[ia];
246
+ const pb = positions[ib];
247
+ const pc = positions[ic];
248
+ if (!pa || !pb || !pc)
249
+ continue;
250
+ if (triangleArea(pa, pb, pc) < DEGENERATE_AREA_EPSILON) {
251
+ findings.push({
252
+ validatorId: ID,
253
+ severity: "warn",
254
+ file,
255
+ message: `${label} has a degenerate (zero-area) triangle at index offset ${t} (vertices ${ia}, ${ib}, ${ic}). Likely duplicate or merged geometry from the export/join step.`,
256
+ fix: "Check the join-by-material step for duplicate vertices at this location; re-triangulate or remove the degenerate face in Blender.",
257
+ evidence: ["cp2077.mesh.glb-triangulation-required"],
258
+ });
259
+ }
260
+ }
261
+ }
262
+ }
263
+ });
264
+ });
265
+ return findings;
266
+ }
267
+ function extensionOf(p) {
268
+ const idx = p.lastIndexOf(".");
269
+ return idx === -1 ? "" : p.slice(idx).toLowerCase();
270
+ }
271
+ export const meshPreexportValidator = {
272
+ id: ID,
273
+ game: GAME,
274
+ groups: GROUPS,
275
+ modTypes: MOD_TYPES,
276
+ summary: "Checks a glTF mesh export for the engine's 65,535-vertex submesh cap, un-applied node transforms, the junk WolvenKit icosphere, a single correctly-named UV layer, and degenerate triangles, before it is imported over a donor mesh.",
277
+ appliesTo(target, _project) {
278
+ if (target.kind !== "file")
279
+ return false;
280
+ const ext = extensionOf(target.path);
281
+ return ext === ".gltf" || ext === ".glb";
282
+ },
283
+ appliesToDescription: "every .gltf or .glb mesh export file in the project",
284
+ needs: { project: true },
285
+ cost: "build",
286
+ severity: "block",
287
+ evidence: EVIDENCE,
288
+ async run(ctx) {
289
+ if (ctx.target.kind !== "file")
290
+ return [];
291
+ const file = ctx.target.path;
292
+ const ext = extensionOf(file);
293
+ if (ext === ".glb") {
294
+ return [
295
+ {
296
+ validatorId: ID,
297
+ severity: "info",
298
+ file,
299
+ message: "This is a binary .glb; ModWright's validator context only reads text, so the JSON chunk cannot be parsed cheaply here and the pre-export checks (vertex cap, transforms, icosphere, UV, triangulation) are skipped for this file. Keep or add a .gltf (JSON, embedded buffers) export alongside it and validate that instead.",
300
+ fix: "Export a .gltf (not .glb) intermediate for this mesh, or add a byte-level reader to ModWright's ValidationContext.",
301
+ evidence: EVIDENCE,
302
+ },
303
+ ];
304
+ }
305
+ let text;
306
+ try {
307
+ text = await ctx.readText(file);
308
+ }
309
+ catch (err) {
310
+ return [
311
+ {
312
+ validatorId: ID,
313
+ severity: "block",
314
+ file,
315
+ message: `Could not read this file: ${err.message}`,
316
+ evidence: EVIDENCE,
317
+ },
318
+ ];
319
+ }
320
+ let doc;
321
+ try {
322
+ doc = JSON.parse(text);
323
+ }
324
+ catch (err) {
325
+ return [
326
+ {
327
+ validatorId: ID,
328
+ severity: "block",
329
+ file,
330
+ message: `Cannot parse this file as glTF JSON: ${err.message}`,
331
+ evidence: EVIDENCE,
332
+ },
333
+ ];
334
+ }
335
+ return checkGltfDoc(doc, file);
336
+ },
337
+ };
338
+ export const MESH_VALIDATORS = [meshPreexportValidator];
@@ -0,0 +1,120 @@
1
+ import * as path from "node:path";
2
+ const ID = "cp2077.history.modsettings-annotation-keys";
3
+ const EVIDENCE = ["ModWright research"];
4
+ const REQUIRED_KEYS = ["mod", "category", "displayName"];
5
+ function isWithin(parent, child) {
6
+ const rel = path.relative(parent, child);
7
+ return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
8
+ }
9
+ function isRedsUnderScripts(target, project) {
10
+ if (target.kind !== "file" || !target.path.toLowerCase().endsWith(".reds"))
11
+ return false;
12
+ return project.sources.some((s) => s.kind === "scripts" && s.exists && isWithin(s.absPath, target.path));
13
+ }
14
+ const ANNOTATION_RE = /^\s*@runtimeProperty\(\s*"ModSettings\.([A-Za-z][A-Za-z0-9_]*)"\s*,\s*"((?:[^"\\]|\\.)*)"\s*\)\s*$/;
15
+ const DECL_RE = /^\s*(?:public\s+|protected\s+|private\s+)?(?:abstract\s+|final\s+)?(?:let|func|class)\s+/;
16
+ const CLASS_DECL_RE = /^(?:public\s+|protected\s+|private\s+)?(?:abstract\s+|final\s+)?class\s+/;
17
+ function collectAnnotationBlocks(text) {
18
+ const lines = text.replace(/\r\n/g, "\n").split("\n");
19
+ const blocks = [];
20
+ let pending;
21
+ let pendingStart = 0;
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const line = lines[i] ?? "";
24
+ const m = ANNOTATION_RE.exec(line);
25
+ if (m) {
26
+ if (!pending) {
27
+ pending = new Map();
28
+ pendingStart = i + 1;
29
+ }
30
+ pending.set(m[1], m[2]);
31
+ continue;
32
+ }
33
+ if (pending) {
34
+ if (DECL_RE.test(line)) {
35
+ blocks.push({ keys: pending, startLine: pendingStart, declLine: i + 1, declText: line.trim() });
36
+ }
37
+ if (line.trim().length === 0 || line.trim().startsWith("//"))
38
+ continue;
39
+ pending = undefined;
40
+ }
41
+ }
42
+ return blocks;
43
+ }
44
+ const modsettingsAnnotationKeys = {
45
+ id: ID,
46
+ game: "cyberpunk2077",
47
+ groups: ["history"],
48
+ modTypes: ["gameplay-tweak"],
49
+ summary: "A ModSettings.* @runtimeProperty block sits on a redscript field (not a class), carries mod/category/displayName, and mod matches this project's own name.",
50
+ appliesTo: isRedsUnderScripts,
51
+ appliesToDescription: "every .reds file under a project source of kind \"scripts\"",
52
+ needs: { project: true },
53
+ cost: "edit",
54
+ severity: "block",
55
+ evidence: EVIDENCE,
56
+ async run(ctx) {
57
+ if (ctx.target.kind !== "file")
58
+ return [];
59
+ const text = await ctx.readText(ctx.target.path);
60
+ const blocks = collectAnnotationBlocks(text);
61
+ const findings = [];
62
+ const projectName = ctx.project.project.identity?.name ?? ctx.project.project.name;
63
+ for (const block of blocks) {
64
+ if (CLASS_DECL_RE.test(block.declText)) {
65
+ findings.push({
66
+ validatorId: ID,
67
+ severity: "block",
68
+ file: ctx.target.path,
69
+ line: block.declLine,
70
+ message: `The ModSettings annotation block sits on "${block.declText}", a class declaration. Mod Settings binds @runtimeProperty("ModSettings.*") annotations to properties, so a block on the class exposes no control.`,
71
+ fix: "Move the @runtimeProperty(\"ModSettings.*\") lines to sit directly above the `let` field they describe, inside the class body.",
72
+ evidence: EVIDENCE,
73
+ });
74
+ continue;
75
+ }
76
+ const missing = REQUIRED_KEYS.filter((k) => !block.keys.has(k));
77
+ if (missing.length > 0) {
78
+ findings.push({
79
+ validatorId: ID,
80
+ severity: "block",
81
+ file: ctx.target.path,
82
+ line: block.declLine,
83
+ message: `The ModSettings annotation block on "${block.declText}" is missing required key(s): ${missing.join(", ")}. The native settings UI needs mod/category/displayName to place a control at all.`,
84
+ fix: `Add @runtimeProperty("ModSettings.<key>", "<value>") for: ${missing.join(", ")}.`,
85
+ evidence: EVIDENCE,
86
+ });
87
+ continue;
88
+ }
89
+ const mod = block.keys.get("mod");
90
+ if (mod !== projectName) {
91
+ findings.push({
92
+ validatorId: ID,
93
+ severity: "block",
94
+ file: ctx.target.path,
95
+ line: block.declLine,
96
+ message: `The ModSettings annotation block on "${block.declText}" declares mod="${mod}", which does not match this project's own name ("${projectName}"). A mismatched mod key places the control on the wrong mod's settings page.`,
97
+ fix: `Set @runtimeProperty("ModSettings.mod", "${projectName}").`,
98
+ evidence: EVIDENCE,
99
+ });
100
+ }
101
+ }
102
+ const bound = blocks.filter((b) => !CLASS_DECL_RE.test(b.declText));
103
+ if (bound.length > 0 && !/ModSettings\.RegisterListenerToClass\s*\(/.test(text)) {
104
+ findings.push({
105
+ validatorId: ID,
106
+ severity: "warn",
107
+ file: ctx.target.path,
108
+ line: bound[0].declLine,
109
+ message: "This file binds ModSettings annotations to properties but never calls ModSettings.RegisterListenerToClass. " +
110
+ "Mod Settings updates the properties only of a registered listener, so the menu control renders and persists while the code keeps reading the default.",
111
+ fix: "Register the settings class as a listener: for a ScriptableSystem, call ModSettings.RegisterListenerToClass(this) from OnAttach() and " +
112
+ "ModSettings.UnregisterListenerToClass(this) from OnDetach() (guard both with @if(ModuleExists(\"ModSettingsModule\")) to keep compiling without Mod Settings); " +
113
+ "for a Codeware ScriptableService, register from OnLoad() and OnReload().",
114
+ evidence: [...EVIDENCE, "cp2077.ui.modsettings.listener-callbacks-require-cb-keyword"],
115
+ });
116
+ }
117
+ return findings;
118
+ },
119
+ };
120
+ export const MODSETTINGS_VALIDATORS = [modsettingsAnnotationKeys];