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,57 @@
1
+ ---
2
+ name: mw-ship
3
+ description: Build, verify the listing, deploy and pre-flight this mod for release.
4
+ model: sonnet
5
+ effort: high
6
+ ---
7
+
8
+ <!-- modwright:asset modwright-ship@${version} -->
9
+
10
+ Take `${project}` from source to a deployed, checked build. Every step here is dry-run
11
+ first; every apply step is the user's decision, not yours.
12
+
13
+ ## 1. Validate
14
+
15
+ `validate` with `includeInfo: true`. `blocking: true` or `counts.block > 0` stops the
16
+ run — explain the findings (see the `mw-validate` skill for how to read `evidence`) and
17
+ hand back. Read `skipped` too: a rule that could not run has not passed.
18
+
19
+ ## 2. Build
20
+
21
+ `build` with `mode: "dry-run"` resolves and describes every step without running a tool.
22
+ Check the step list, the `variant` if one applies, and the resolved `version`. Then
23
+ `mode: "apply"`.
24
+
25
+ The cheap validators run again before the first pack step and refuse the build on any
26
+ block. Do not reach for `noValidate` — if a block is wrong, it is a rule to fix or
27
+ suppress with a reason, not a gate to skip. On failure the stage tree is kept: say where
28
+ it is.
29
+
30
+ ## 3. Deploy
31
+
32
+ `deploy` with `mode: "dry-run"` first. It resolves every target, refuses an artifact this
33
+ build did not produce or one older than its sources, and reports what it would copy,
34
+ prune and back up. Note `deployMode` (`pak` or `loose`) and, for a pak deploy blocked by
35
+ an existing junction, that `unlink` removes the link only.
36
+
37
+ Then `mode: "apply"` once the user confirms. Report `deferred` targets (the game
38
+ still held the file), the reload recommendation the result carries, and where the backup
39
+ went.
40
+
41
+ ## 4. Pre-flight
42
+
43
+ - `reload_plan` on the deployed paths if the deploy result's recommendation needs
44
+ restating, and tell the user what to relaunch.
45
+ - `run_tests` (or the `mw-verify` skill) for anything the release claims to fix.
46
+ - `rollback` with no `backupId` lists the backups this project has, newest stamp last —
47
+ name the one this deploy wrote so the user can undo it. Restoring is
48
+ `rollback` with that `backupId` and `mode: "apply"`.
49
+
50
+ ModWright does not publish. Summarise for the user what was built, what was
51
+ deployed where, what is still on the manual checklist, and the version to tag.
52
+
53
+ ## Stop and ask
54
+
55
+ Stop before every apply. Stop on any block finding, any failed build step, any deferred
56
+ deploy target, and before `unlink`, `noValidate` or a rollback — each of those either
57
+ changes the game install or drops a safety check.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: mw-validate
3
+ description: Run ModWright's validators over this mod and explain every finding.
4
+ argument-hint: [--changed] [--rule <id>]
5
+ model: sonnet
6
+ effort: medium
7
+ ---
8
+
9
+ <!-- modwright:asset modwright-validate@${version} -->
10
+
11
+ Run ModWright's `validate` tool over this project (`${project}`, game `${game}`) and
12
+ explain what it reports. You are the explainer, not the fixer: nothing is edited until
13
+ the user says so.
14
+
15
+ ## Call it
16
+
17
+ `validate` takes `path` (a `modwright.json` or a directory to search up from — omit for
18
+ the repo root), `groups`, `cheapOnly`, `changedFiles`, `includeInfo` and `installPath`.
19
+
20
+ - No argument: `validate` with `includeInfo: true` for the first pass of a session.
21
+ - `--changed`: pass the absolute paths of the files edited so far as `changedFiles`;
22
+ file rules then run only on those. It is a filter, not a faster mode.
23
+ - `--rule <id>`: `validate` has **no** per-rule input. Pass the rule's group in `groups`
24
+ when you know it, otherwise run unfiltered and report only the findings whose
25
+ `validatorId` matches. Say which of the two you did.
26
+ - `cheapOnly: true` is the on-every-edit subset — use it while iterating, never as the
27
+ final word before a build.
28
+
29
+ ## Read the report
30
+
31
+ `counts`, `blocking` and `suppressed` are computed over unsuppressed findings only.
32
+ `blocking: false` with `counts.block === 0` is the clean result; `findings: []` alone is
33
+ not, because a rule that could not run is in `skipped`, not in `findings`.
34
+
35
+ - Walk `skipped` first. Each entry names its reason — no index (`build_index`), a tool
36
+ that was not found (`check_toolchain`), no install. Report those as unknowns, never as
37
+ passes.
38
+ - For each finding, quote `file`, `line`, `message` and `fix` as the tool gave them.
39
+ - Every finding carries `evidence`: knowledge fact ids. **Do not explain a rule from
40
+ memory.** For each id call `lookup` with `game: "${game}"`, `kind: "facts"`,
41
+ `query: { text: "<fact id>", full: "1" }`, and report the fact's `status`
42
+ (verified / unverified / contradicted / community / inferred) alongside the claim. An
43
+ `unverified` or `contradicted` fact behind a finding is worth saying out loud.
44
+ - A finding with `suppressedBy` is already a recorded decision. Show it and move on.
45
+
46
+ ## Stop and ask
47
+
48
+ Stop and hand back to the user before you: edit any file to fix a finding; add or
49
+ widen an entry in `.modwright/suppressions.json` (a suppression needs their reason, in
50
+ their words, not yours); or conclude a rule is a false positive. Propose the change,
51
+ name the finding it answers, and wait.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: mw-verify
3
+ description: Run this mod's test plan against the in-game bridge and record the result.
4
+ argument-hint: [plan] [--rows <ids>]
5
+ model: sonnet
6
+ effort: high
7
+ ---
8
+
9
+ <!-- modwright:asset modwright-verify@${version} -->
10
+
11
+ Verify `${project}` in the running game. ModWright never launches the game, never sends a
12
+ reload command, and never decides on the user's behalf that something is verified.
13
+
14
+ ## 1. Is what is deployed the thing you are testing?
15
+
16
+ If a deploy happened this session, pass its destination paths to `reload_plan` as
17
+ `paths` (an array, at least one). It answers `hot`, `hot-degraded`, `save-reload`,
18
+ `restart` or `unknown` ("restart to be sure"). Tell the user what the answer
19
+ requires *before* they load a save — a stale build is the usual mystery failure.
20
+
21
+ ## 2. Bridge
22
+
23
+ `bridge` with `action: "status"` and `game: "${game}"` (or `path`) reports heartbeat
24
+ freshness and whether `protocolVersion` matches. If the bridge is not staged in this
25
+ project, `action: "stage"` copies it in (`mode: "dry-run"` first, then `"apply"`), and it
26
+ reaches the game through `deploy --variant withBridge`.
27
+
28
+ A stale or missing heartbeat means the game is not running with the bridge armed. Say so
29
+ and stop; do not run the plan against a bridge that cannot answer.
30
+
31
+ ## 3. Run the plan
32
+
33
+ `run_tests` takes `plan` (a planId from a plan's own header, or a path to its YAML —
34
+ usually `tests/authored.yaml`), `rows` (the `--rows` ids, omit for all), `gates`, `mode`,
35
+ `windowSeconds`, `allowMutate` and `timeoutSeconds`.
36
+
37
+ - Always `mode: "dry-run"` first: it shows the combined bridge request and the full
38
+ checklist without arming anything.
39
+ - Then `mode: "apply"`. Rows with a probe go to the bridge in one request; rows without
40
+ one are checklist items for the user to answer.
41
+ - `allowMutate: true` lets mutating probes run. Ask first, every time.
42
+ - A row on an `unverified` probe kind that passes is capped below `verified` on purpose —
43
+ report that cap, do not round it up.
44
+
45
+ ## 4. Record
46
+
47
+ `run_tests` on apply writes observed/status/evidence back into the plan YAML and, for a
48
+ row naming a ledger `claim`, records verified/contradicted against that claim itself —
49
+ do not re-record it. Read it back with `ledger` `action: "list"`/`"show"`, and
50
+ `action: "check"` to confirm every verified claim's evidence still exists on disk.
51
+
52
+ Reach for `ledger` `action: "record"` (`mode: "apply"`) only for a manual corroboration
53
+ the user dictates — and nothing reaches verified without an evidence block.
54
+
55
+ ## Stop and ask
56
+
57
+ Stop if the bridge heartbeat is stale, if a manual row needs an in-game observation, if a
58
+ probe times out (report which of the named timeout outcomes it was), or if a row failed.
59
+ A failed row is a finding to hand back, not something to re-run until it passes.
@@ -0,0 +1,177 @@
1
+ """Decode Nivalis Nights `Resources/` definition assets to JSONL, for ModWright's vanilla index.
2
+
3
+ Run by the nivalisnights index builder (src/surfaces/nivalisnights/index/build.ts) through
4
+ the `unitypy-python` tool: a Python with UnityPy and TypeTreeGeneratorAPI installed. Reads
5
+ only the user's own install; writes only under --out. The notes below record why each
6
+ step is shaped as it is:
7
+
8
+ - The build strips type trees, so MonoBehaviour fields are decoded with type trees
9
+ generated from Cpp2IL dummy DLLs made with the attributeanalyzer + attributeinjector
10
+ processors (TypeTreeGeneratorAPI's own IL2CPP loader fails on this build).
11
+ - The generated base nodes miss the align after m_Enabled, so m_Script is read from the
12
+ raw bytes (FileID int32 at 16, PathID int64 at 20) and the shifted base fields are dropped.
13
+ - MonoScripts live in globalgamemanagers.assets, which is always loaded.
14
+ - UnityPy loads whole files, so each folder loads only the files its entries live in.
15
+
16
+ Output, under --out:
17
+ <folder>.jsonl one record per decoded object: {"path", "folder", "key", "script", "data"}
18
+ where key is "<file>:<path_id>" and every PPtr inside data is either
19
+ null (path id 0) or {"$ref": "<file>:<path_id>"}.
20
+ summary.json {"unity", "folders": {folder: {"entries", "decoded", "scripts", "errors"}}}
21
+
22
+ Usage:
23
+ python extract_resources.py --data "<root>/Nivalis Nights_Data" --dlls <dummydll dir>
24
+ --out <dir> --folders itemtypes,recipes [--follow peopledatabase]
25
+ """
26
+
27
+ import argparse
28
+ import collections
29
+ import json
30
+ import os
31
+ import struct
32
+ import sys
33
+
34
+ import UnityPy
35
+ from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator
36
+
37
+ UNITY_VERSION = "2020.3.44f1"
38
+ BASE_FIELDS = ("m_GameObject", "m_Enabled", "m_Script")
39
+
40
+
41
+ def main() -> int:
42
+ ap = argparse.ArgumentParser()
43
+ ap.add_argument("--data", required=True)
44
+ ap.add_argument("--dlls", required=True)
45
+ ap.add_argument("--out", required=True)
46
+ ap.add_argument("--folders", required=True, help="comma-separated top-level Resources folders")
47
+ ap.add_argument("--follow", default="", help="folders whose referenced objects are decoded too, as <folder>.refs")
48
+ args = ap.parse_args()
49
+
50
+ UnityPy.config.FALLBACK_UNITY_VERSION = UNITY_VERSION
51
+ gen = TypeTreeGenerator(UNITY_VERSION)
52
+ gen.load_local_dll_folder(args.dlls)
53
+ os.makedirs(args.out, exist_ok=True)
54
+
55
+ ggm = next(iter(UnityPy.load(os.path.join(args.data, "globalgamemanagers")).files.values()))
56
+ rm = next(o for o in ggm.objects.values() if o.type.name == "ResourceManager").read()
57
+
58
+ def ext_name(pptr) -> str:
59
+ return os.path.basename(ggm.externals[pptr.file_id - 1].path if pptr.file_id else "globalgamemanagers").lower()
60
+
61
+ folders = [f.strip().lower() for f in args.folders.split(",") if f.strip()]
62
+ follow = {f.strip().lower() for f in args.follow.split(",") if f.strip()}
63
+ by_folder = collections.defaultdict(list)
64
+ for path, pptr in rm.m_Container:
65
+ top = path.split("/")[0]
66
+ if top in folders:
67
+ by_folder[top].append((path, pptr))
68
+
69
+ summary = {"unity": UNITY_VERSION, "folders": {}}
70
+ for folder in folders:
71
+ wanted = by_folder.get(folder, [])
72
+ needed = sorted({ext_name(pp) for _, pp in wanted} - {"globalgamemanagers", "globalgamemanagers.assets"})
73
+ env = UnityPy.load(os.path.join(args.data, "globalgamemanagers.assets"), *[os.path.join(args.data, n) for n in needed])
74
+ env.typetree_generator = gen
75
+ files = {os.path.basename(k).lower(): v for k, v in env.files.items()}
76
+ scripts_cache = {}
77
+
78
+ def script_of(obj) -> str:
79
+ sf = obj.assets_file
80
+ fid, pid = struct.unpack_from("<iq", obj.get_raw_data(), 16)
81
+ owner = os.path.basename(sf.externals[fid - 1].path).lower() if fid else os.path.basename(sf.name).lower()
82
+ if (owner, pid) not in scripts_cache:
83
+ ms = files[owner].objects[pid].read_typetree()
84
+ scripts_cache[(owner, pid)] = (ms["m_Namespace"] + "." if ms["m_Namespace"] else "") + ms["m_ClassName"]
85
+ return scripts_cache[(owner, pid)]
86
+
87
+ def decode(obj, path, out, stats, folder_name):
88
+ file_name = os.path.basename(obj.assets_file.name).lower()
89
+ externals = [os.path.basename(e.path).lower() for e in obj.assets_file.externals]
90
+ refs = []
91
+
92
+ def norm(v):
93
+ if isinstance(v, dict):
94
+ if set(v.keys()) == {"m_FileID", "m_PathID"}:
95
+ if not v["m_PathID"]:
96
+ return None
97
+ fid = v["m_FileID"]
98
+ target = file_name if fid == 0 else (externals[fid - 1] if 0 < fid <= len(externals) else f"?{fid}")
99
+ key = f"{target}:{v['m_PathID']}"
100
+ refs.append(key)
101
+ return {"$ref": key}
102
+ return {k: norm(x) for k, x in v.items()}
103
+ if isinstance(v, list):
104
+ return [norm(x) for x in v]
105
+ return v
106
+
107
+ if obj.type.name != "MonoBehaviour":
108
+ stats["errors"][f"not a MonoBehaviour ({obj.type.name})"] += 1
109
+ return refs
110
+ try:
111
+ tree = obj.read_typetree()
112
+ cls = script_of(obj)
113
+ except Exception as e: # one bad object never stops the folder
114
+ stats["errors"][f"{type(e).__name__}: {str(e)[:120]}"] += 1
115
+ return refs
116
+ data = norm({k: v for k, v in tree.items() if k not in BASE_FIELDS})
117
+ stats["scripts"][cls] += 1
118
+ stats["decoded"] += 1
119
+ record = {"path": path, "folder": folder_name, "key": f"{file_name}:{obj.path_id}", "script": cls, "data": data}
120
+ out.write(json.dumps(record, ensure_ascii=False, default=str) + "\n")
121
+ return refs
122
+
123
+ def new_stats(entries):
124
+ return {"entries": entries, "decoded": 0, "scripts": collections.Counter(), "errors": collections.Counter()}
125
+
126
+ stats = new_stats(len(wanted))
127
+ followed = set()
128
+ with open(os.path.join(args.out, f"{folder}.jsonl"), "w", encoding="utf-8") as out:
129
+ for path, pptr in wanted:
130
+ sf = files.get(ext_name(pptr))
131
+ obj = sf.objects.get(pptr.path_id) if sf else None
132
+ if obj is None:
133
+ stats["errors"]["entry's file not loaded or object missing"] += 1
134
+ continue
135
+ for ref in decode(obj, path, out, stats, folder):
136
+ followed.add(ref)
137
+ summary["folders"][folder] = {k: (dict(v) if isinstance(v, collections.Counter) else v) for k, v in stats.items()}
138
+ print(f"{folder}: {stats['decoded']}/{stats['entries']} decoded", flush=True)
139
+
140
+ if folder in follow:
141
+ # Two levels: the folder's references (PeopleDatabase -> the NPCs), then
142
+ # theirs (an NPC -> its demographic group, action list). Only MonoBehaviours
143
+ # are decoded; a reference to a prefab, sprite or mesh is counted, not an error.
144
+ seen = set()
145
+ frontier = followed
146
+ for suffix in ("refs", "refs2"):
147
+ name = f"{folder}.{suffix}"
148
+ todo = sorted(k for k in frontier if k not in seen)
149
+ seen.update(todo)
150
+ fstats = new_stats(0)
151
+ fstats["skipped_non_monobehaviour"] = 0
152
+ nxt = set()
153
+ with open(os.path.join(args.out, f"{name}.jsonl"), "w", encoding="utf-8") as out:
154
+ for key in todo:
155
+ fname, _, pid = key.rpartition(":")
156
+ sf = files.get(fname)
157
+ obj = sf.objects.get(int(pid)) if sf and pid.lstrip("-").isdigit() else None
158
+ if obj is None:
159
+ fstats["skipped_unloaded_file"] = fstats.get("skipped_unloaded_file", 0) + 1
160
+ continue
161
+ if obj.type.name != "MonoBehaviour":
162
+ fstats["skipped_non_monobehaviour"] += 1
163
+ continue
164
+ fstats["entries"] += 1
165
+ nxt.update(decode(obj, key, out, fstats, name))
166
+ summary["folders"][name] = {k: (dict(v) if isinstance(v, collections.Counter) else v) for k, v in fstats.items()}
167
+ print(f"{name}: {fstats['decoded']}/{fstats['entries']} decoded", flush=True)
168
+ frontier = nxt
169
+ del env
170
+
171
+ with open(os.path.join(args.out, "summary.json"), "w", encoding="utf-8") as f:
172
+ json.dump(summary, f, indent=1)
173
+ return 0
174
+
175
+
176
+ if __name__ == "__main__":
177
+ sys.exit(main())
@@ -0,0 +1,5 @@
1
+ {
2
+ "RequiredVersion": 21,
3
+ "ModTable": "ModWrightBridge",
4
+ "FeatureFlags": ["Lua"]
5
+ }
@@ -0,0 +1,55 @@
1
+ -- MODWRIGHT-BRIDGE-DO-NOT-SHIP
2
+ --
3
+ -- ModWrightBridge — CLIENT bootstrap: heartbeat and client probes only.
4
+ --
5
+ -- It loads at the main menu, which is what makes the `bg3.client.loaded` row answerable
6
+ -- without a save ("main menu is enough; no save needed"), the same client-marker pattern a
7
+ -- shipped SE mod uses.
8
+ --
9
+ -- It deliberately does NOT poll for requests and does NOT write result files. API.md
10
+ -- §"Client / Server States": the client and server Lua states "are completely separated from
11
+ -- each other and cannot access the internal state of each other", and the protocol has
12
+ -- exactly one request file and one result file per request id, so two pollers writing the
13
+ -- same result would race. The server relays this state's answer out of the heartbeat instead
14
+ -- (Probes.lua, `bg3.client.loaded`).
15
+ --
16
+ -- ============================================================================
17
+ -- Ext API used in this file
18
+ -- ============================================================================
19
+ -- API.md §"Getting Started" / "Bootstrap Scripts": "the extender will attempt to load ...
20
+ -- `BootstrapClient.lua` on the client side"; `Ext.Require` at module startup only.
21
+ -- API.md §"Engine and SE Events": `SessionLoaded`, `ResetCompleted`.
22
+ -- API.md §"SE Events": `Ext.Events.<EVENT_NAME>:Subscribe(fun)`.
23
+
24
+ local Log = Ext.Require("ModWrightBridge/Log.lua")
25
+ local Protocol = Ext.Require("ModWrightBridge/Protocol.lua")
26
+ local ProbesClient = Ext.Require("ModWrightBridge/ProbesClient.lua")
27
+
28
+ -- Only the server state writes ModWright_log.json (the protocol has one breadcrumb file);
29
+ -- the client keeps its breadcrumbs on the SE console so the two states never race over it.
30
+ Log.mirrorToFile = false
31
+
32
+ Protocol.init({
33
+ context = "client",
34
+ dispatcher = ProbesClient,
35
+ heartbeatFile = Protocol.FILES.heartbeatClient,
36
+ })
37
+
38
+ local function mark(stage)
39
+ ProbesClient.stage = stage
40
+ local res = ProbesClient.run("bg3.client.loaded")
41
+ pcall(function() Protocol.writeHeartbeat() end)
42
+ Log.info(("client %s (marker %s, heartbeat %s)")
43
+ :format(stage, tostring(res.status), Protocol.FILES.heartbeatClient))
44
+ end
45
+
46
+ mark("bootstrap")
47
+ pcall(function() Protocol.startHeartbeat() end)
48
+
49
+ Ext.Events.SessionLoaded:Subscribe(function()
50
+ pcall(function() mark("SessionLoaded") end)
51
+ end)
52
+
53
+ Ext.Events.ResetCompleted:Subscribe(function()
54
+ pcall(function() mark("ResetCompleted") end)
55
+ end)
@@ -0,0 +1,81 @@
1
+ -- MODWRIGHT-BRIDGE-DO-NOT-SHIP
2
+ --
3
+ -- ModWrightBridge — SERVER bootstrap. This is ModWright's author-only verification bridge,
4
+ -- built to the ModWright request/result protocol. It is never shipped in a mod:
5
+ -- `bg3.history.bridge-never-ships` (src/surfaces/baldursgate3/validators/bridge.ts) blocks a
6
+ -- build whose output carries this folder or the banner on the first line above.
7
+ --
8
+ -- WHAT THIS MOD DOES, IN ONE PARAGRAPH: it writes a heartbeat every 2 s while loaded, polls
9
+ -- for an armed request file every 1 s, runs the probes that request names, and writes a
10
+ -- result followed by a `done` marker carrying the result's byte length. It reads. It never
11
+ -- calls an Osiris action, never forces a cast, and writes no PersistentVars, ModVars or
12
+ -- UserVars (API.md §"Persistence": "PersistentVars are deprecated and should not be used").
13
+ --
14
+ -- ============================================================================
15
+ -- Registration shape and its citations
16
+ -- ============================================================================
17
+ -- API.md §"Getting Started" / "Bootstrap Scripts":
18
+ -- "the extender will attempt to load `BootstrapServer.lua` on the server side"; and
19
+ -- "The `Ext.Require` function is the extender's version of the Lua built-in `require`" —
20
+ -- "`Ext.Require` should only be called during module startup". Hence every require below
21
+ -- is at chunk scope, as a shipped SE mod does it.
22
+ -- API.md §"Engine and SE Events - `Ext.Events`":
23
+ -- `SessionLoaded` | "Thrown when the game session was set up."
24
+ -- `ResetCompleted` | "Thrown when `Ext.Debug.Reset()` or `reset` console command completes
25
+ -- on the client or server. Indicates that the Lua state was reloaded."
26
+ -- API.md §"SE Events":
27
+ -- `Ext.Events.<EVENT_NAME>:Subscribe(fun)`
28
+ --
29
+ -- ONE SessionLoaded subscription, every call inside it `pcall`-guarded: a shape proven in a
30
+ -- shipped SE mod. That pattern also explains why the poll timer exists at all: a mod that
31
+ -- reads its flag file INSIDE SessionLoaded never sees a file dropped after the session is
32
+ -- up, which is why the bridge polls.
33
+ --
34
+ -- ResetCompleted is subscribed for two reasons, both observed in game: a console `reset`
35
+ -- reloads the Lua state WITHOUT re-running the session (so SessionLoaded may never fire
36
+ -- again and the poll timer must be started from here too), and "`reset` mid-session degrades
37
+ -- Osiris ... Osi queries return junk and Osi calls silently no-op afterward" — which is what
38
+ -- `osirisDegraded` records and what probe `bg3.osiris.health` measures.
39
+
40
+ local Log = Ext.Require("ModWrightBridge/Log.lua")
41
+ local Protocol = Ext.Require("ModWrightBridge/Protocol.lua")
42
+ local Watch = Ext.Require("ModWrightBridge/Watch.lua")
43
+ local Probes = Ext.Require("ModWrightBridge/Probes.lua")
44
+
45
+ Protocol.init({
46
+ context = "server",
47
+ dispatcher = Probes,
48
+ heartbeatFile = Protocol.FILES.heartbeatServer,
49
+ })
50
+
51
+ local seVersion = "unknown"
52
+ pcall(function() seVersion = tostring(Ext.Utils.Version()) end)
53
+
54
+ local probeKinds = 0
55
+ for _ in pairs(Probes.handlers) do probeKinds = probeKinds + 1 end
56
+
57
+ Log.info(("server bootstrap loaded (bridge %s, protocolVersion %d, SE v%s, %d probe kinds, Watch %s)")
58
+ :format(Protocol.BRIDGE_VERSION, Protocol.PROTOCOL_VERSION, seVersion, probeKinds, tostring(Watch ~= nil)))
59
+
60
+ -- The heartbeat starts at load, not at SessionLoaded: it is rewritten every 2 s while the
61
+ -- bridge is loaded, and `bridge status` needs one at the main menu to
62
+ -- tell "not deployed" from "deployed, game not in a session".
63
+ pcall(function() Protocol.startHeartbeat() end)
64
+
65
+ Ext.Events.SessionLoaded:Subscribe(function()
66
+ pcall(function() Protocol.startPolling() end)
67
+ pcall(function() Protocol.writeHeartbeat() end)
68
+ pcall(function()
69
+ Log.info("SessionLoaded: polling " .. Protocol.FILES.request ..
70
+ " every " .. Protocol.POLL_MS .. " ms; arm with `bridge arm` or by writing the request file by hand")
71
+ end)
72
+ end)
73
+
74
+ Ext.Events.ResetCompleted:Subscribe(function()
75
+ pcall(function() Protocol.markOsirisDegraded() end)
76
+ -- A `reset` clears the in-memory consumed-nonce set with the rest of the Lua state; that
77
+ -- is exactly why a request carries `armedUntil`, so a stale request cannot
78
+ -- silently execute a second time.
79
+ pcall(function() Protocol.startPolling() end)
80
+ pcall(function() Protocol.writeHeartbeat() end)
81
+ end)
@@ -0,0 +1,96 @@
1
+ -- MODWRIGHT-BRIDGE-DO-NOT-SHIP
2
+ --
3
+ -- ModWrightBridge / Log.lua — console breadcrumbs with a rolling file mirror.
4
+ --
5
+ -- Follows the common SE mod logging pattern: every line is printed AND appended to a rolling
6
+ -- 250-entry JSON array in the SE storage dir, because the SE console often cannot be watched
7
+ -- live while the game runs. The breadcrumb file is `ModWright_log.json` with a 250-line cap,
8
+ -- and every `Ext.Utils.Print` carries the `[ModWrightBridge]` prefix.
9
+ --
10
+ -- This module deliberately has NO dependencies (not even Protocol) so that a failure anywhere
11
+ -- else can still be reported. It is also the ONE place that uses `Ext.Json.Stringify` on a
12
+ -- table of its own making: the buffer is never empty when it is written, so the empty-table
13
+ -- array/object ambiguity that forced Protocol's own encoder (see Protocol.lua) cannot bite
14
+ -- here, and nothing on the ModWright side parses this file — it is a human breadcrumb.
15
+ --
16
+ -- API.md §"Utils" / §"Helper/aliased functions":
17
+ -- `_P()`: Equivalent to `Ext.Utils.Print()`
18
+ -- API.md §"I/O":
19
+ -- `Ext.IO.SaveFile(path, content): boolean` -- Writes content to a file. Creates missing parent directories.
20
+ -- API.md §"JSON support - `Ext.Json`":
21
+ -- `Ext.Json.Stringify(value, [options])`
22
+ -- API.md §"Timers - `Ext.Timer`":
23
+ -- `Ext.Timer.MonotonicTime(): int64` -- monotonic system time in milliseconds
24
+
25
+ local Log = {}
26
+
27
+ Log.PREFIX = "[ModWrightBridge]"
28
+ Log.FILE = "ModWright_log.json"
29
+ Log.MAX_LINES = 250
30
+
31
+ -- Set false by BootstrapClient: the client Lua state must never write ModWright_log.json,
32
+ -- or the two states race each other over the same file (there is exactly one breadcrumb
33
+ -- file, written by the server).
34
+ Log.mirrorToFile = true
35
+
36
+ Log.buffer = {}
37
+
38
+ --- Monotonic milliseconds; total (never throws, never returns nil).
39
+ --- API.md §"Timers": `Ext.Timer.MonotonicTime(): int64`. Falls back through other clocks
40
+ --- when the call is unavailable on an older extender.
41
+ function Log.monotonic()
42
+ local ok, t = pcall(function() return Ext.Timer.MonotonicTime() end)
43
+ if ok then
44
+ local n = tonumber(t)
45
+ if n then return n end
46
+ end
47
+ local ok2, t2 = pcall(function() return Ext.Utils.MonotonicTime() end)
48
+ if ok2 then
49
+ local n2 = tonumber(t2)
50
+ if n2 then return n2 end
51
+ end
52
+ local ok3, c = pcall(function() return os.clock() end)
53
+ if ok3 and type(c) == "number" then return math.floor(c * 1000) end
54
+ return 0
55
+ end
56
+
57
+ local function mirror(line)
58
+ if not Log.mirrorToFile then return end
59
+ pcall(function()
60
+ local buf = Log.buffer
61
+ buf[#buf + 1] = { t = Log.monotonic(), line = line }
62
+ while #buf > Log.MAX_LINES do table.remove(buf, 1) end
63
+ Ext.IO.SaveFile(Log.FILE, Ext.Json.Stringify(buf))
64
+ end)
65
+ end
66
+
67
+ --- Print one prefixed line and mirror it. Never throws.
68
+ function Log.write(msg)
69
+ local line = Log.PREFIX .. " " .. tostring(msg)
70
+ pcall(function() Ext.Utils.Print(line) end)
71
+ mirror(line)
72
+ return line
73
+ end
74
+
75
+ --- Ordinary breadcrumb.
76
+ function Log.info(msg)
77
+ return Log.write(msg)
78
+ end
79
+
80
+ --- Error breadcrumb. Same transport; the word ERROR is what a reader greps for.
81
+ function Log.err(where, msg)
82
+ return Log.write("ERROR [" .. tostring(where) .. "] " .. tostring(msg))
83
+ end
84
+
85
+ --- Wrap a callback so a raw error can never escape into an engine/Osiris callback.
86
+ --- The guarded-callback pattern from a shipped SE mod; its reason — a bad error path leaves
87
+ --- Osiris returning junk — applies to every listener and timer here.
88
+ function Log.guarded(where, fn)
89
+ return function(...)
90
+ local ok, err = pcall(fn, ...)
91
+ if not ok then Log.err(where, err) end
92
+ return ok
93
+ end
94
+ end
95
+
96
+ return Log