davinci-resolve-mcp 2.57.4 → 2.58.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 (328) hide show
  1. package/AGENTS.md +20 -0
  2. package/CHANGELOG.md +197 -2
  3. package/LICENSE +1 -1
  4. package/README.md +73 -1
  5. package/bin/davinci-resolve-advanced-mcp.mjs +20 -0
  6. package/docs/README.md +19 -0
  7. package/docs/SKILL.md +62 -1
  8. package/docs/contributing.md +1 -1
  9. package/docs/guides/control-panel.md +9 -0
  10. package/docs/kernels/README.md +21 -0
  11. package/docs/kernels/audio-fairlight-kernel.md +22 -0
  12. package/docs/kernels/color-grade-kernel.md +51 -0
  13. package/docs/kernels/fusion-composition-kernel.md +17 -0
  14. package/docs/kernels/media-pool-ingest-kernel.md +20 -0
  15. package/docs/kernels/render-deliver-kernel.md +32 -0
  16. package/docs/kernels/timeline-conform-interchange-kernel.md +39 -0
  17. package/docs/kernels/timeline-edit-kernel.md +17 -0
  18. package/docs/process/release-process.md +29 -7
  19. package/docs/reference/api-coverage.md +1 -1
  20. package/docs/reference/api-limitations.md +38 -1
  21. package/install.py +27 -4
  22. package/package.json +23 -2
  23. package/resolve-advanced/README.md +194 -0
  24. package/resolve-advanced/package.json +50 -0
  25. package/resolve-advanced/server/black-balance.mjs +92 -0
  26. package/resolve-advanced/server/capabilities.mjs +86 -0
  27. package/resolve-advanced/server/cdl-io.mjs +76 -0
  28. package/resolve-advanced/server/contrast-normalize.mjs +180 -0
  29. package/resolve-advanced/server/db-patch.mjs +91 -0
  30. package/resolve-advanced/server/deliverable-entities.mjs +86 -0
  31. package/resolve-advanced/server/deliverable-qc.mjs +270 -0
  32. package/resolve-advanced/server/editorial.mjs +370 -0
  33. package/resolve-advanced/server/exposure-level.mjs +129 -0
  34. package/resolve-advanced/server/extract-frames.mjs +196 -0
  35. package/resolve-advanced/server/ffprobe-media.mjs +88 -0
  36. package/resolve-advanced/server/gamut-legal.mjs +112 -0
  37. package/resolve-advanced/server/grade-body-patch.mjs +246 -0
  38. package/resolve-advanced/server/grade-transfer.mjs +66 -0
  39. package/resolve-advanced/server/group-grade-read.mjs +137 -0
  40. package/resolve-advanced/server/index.mjs +105 -0
  41. package/resolve-advanced/server/lib.mjs +84 -0
  42. package/resolve-advanced/server/libs.mjs +65 -0
  43. package/resolve-advanced/server/lineage-db.mjs +479 -0
  44. package/resolve-advanced/server/lut-apply.mjs +61 -0
  45. package/resolve-advanced/server/match-to-reference.mjs +173 -0
  46. package/resolve-advanced/server/media-inventory.mjs +169 -0
  47. package/resolve-advanced/server/media-ops.mjs +243 -0
  48. package/resolve-advanced/server/node-meta-db.mjs +62 -0
  49. package/resolve-advanced/server/node-provenance.mjs +102 -0
  50. package/resolve-advanced/server/offline-ref-db.mjs +420 -0
  51. package/resolve-advanced/server/offline-ref.mjs +179 -0
  52. package/resolve-advanced/server/project-db.mjs +233 -0
  53. package/resolve-advanced/server/provenance-audit.mjs +210 -0
  54. package/resolve-advanced/server/qc-frame.mjs +224 -0
  55. package/resolve-advanced/server/qc-sampler.mjs +67 -0
  56. package/resolve-advanced/server/readback.mjs +77 -0
  57. package/resolve-advanced/server/render-manifest.mjs +93 -0
  58. package/resolve-advanced/server/reverse-clip-db.mjs +270 -0
  59. package/resolve-advanced/server/runner-apply-contract.mjs +64 -0
  60. package/resolve-advanced/server/runner.mjs +244 -0
  61. package/resolve-advanced/server/saturation-match.mjs +108 -0
  62. package/resolve-advanced/server/scope-read.mjs +257 -0
  63. package/resolve-advanced/server/season-look.mjs +96 -0
  64. package/resolve-advanced/server/shot-intent.mjs +82 -0
  65. package/resolve-advanced/server/shot-match.mjs +147 -0
  66. package/resolve-advanced/server/skin-match.mjs +205 -0
  67. package/resolve-advanced/server/spec-compile.mjs +217 -0
  68. package/resolve-advanced/server/tone-curve-transfer.mjs +184 -0
  69. package/resolve-advanced/server/tool-catalog.mjs +265 -0
  70. package/resolve-advanced/server/tools/audio.mjs +54 -0
  71. package/resolve-advanced/server/tools/audio_plan.mjs +58 -0
  72. package/resolve-advanced/server/tools/capabilities.mjs +16 -0
  73. package/resolve-advanced/server/tools/color_trace.mjs +165 -0
  74. package/resolve-advanced/server/tools/conform.mjs +291 -0
  75. package/resolve-advanced/server/tools/deliverable.mjs +105 -0
  76. package/resolve-advanced/server/tools/drp.mjs +232 -0
  77. package/resolve-advanced/server/tools/drt.mjs +200 -0
  78. package/resolve-advanced/server/tools/drx.mjs +943 -0
  79. package/resolve-advanced/server/tools/editorial.mjs +69 -0
  80. package/resolve-advanced/server/tools/fairlight.mjs +108 -0
  81. package/resolve-advanced/server/tools/fusion.mjs +64 -0
  82. package/resolve-advanced/server/tools/media.mjs +102 -0
  83. package/resolve-advanced/server/tools/offline_ref.mjs +123 -0
  84. package/resolve-advanced/server/tools/pipeline.mjs +137 -0
  85. package/resolve-advanced/server/tools/project_db.mjs +200 -0
  86. package/resolve-advanced/server/tools/project_read.mjs +212 -0
  87. package/resolve-advanced/server/tools/provenance.mjs +73 -0
  88. package/resolve-advanced/server/verify-grade.mjs +113 -0
  89. package/resolve-advanced/server/white-balance-match.mjs +122 -0
  90. package/resolve-advanced/vendor/audio/format-converter.js +330 -0
  91. package/resolve-advanced/vendor/audio/split.js +173 -0
  92. package/resolve-advanced/vendor/audio/trim.js +188 -0
  93. package/resolve-advanced/vendor/audio-fairlight/index.js +391 -0
  94. package/resolve-advanced/vendor/conform-qc/adapters/frame-sampler.js +56 -0
  95. package/resolve-advanced/vendor/conform-qc/adapters/local-ffmpeg-sampler.js +65 -0
  96. package/resolve-advanced/vendor/conform-qc/adapters/rendernode-ffmpeg-sampler.js +50 -0
  97. package/resolve-advanced/vendor/conform-qc/adapters/resolve/driver.py +176 -0
  98. package/resolve-advanced/vendor/conform-qc/adapters/resolve-driver.js +61 -0
  99. package/resolve-advanced/vendor/conform-qc/adapters/resolve-headless-driver.js +86 -0
  100. package/resolve-advanced/vendor/conform-qc/adapters/vision-validator.js +75 -0
  101. package/resolve-advanced/vendor/conform-qc/cli.js +63 -0
  102. package/resolve-advanced/vendor/conform-qc/compare/advisory.js +106 -0
  103. package/resolve-advanced/vendor/conform-qc/compare/decode.js +36 -0
  104. package/resolve-advanced/vendor/conform-qc/compare/index.js +61 -0
  105. package/resolve-advanced/vendor/conform-qc/compare/locate.js +70 -0
  106. package/resolve-advanced/vendor/conform-qc/compare/metrics.js +409 -0
  107. package/resolve-advanced/vendor/conform-qc/docs/runbook.md +72 -0
  108. package/resolve-advanced/vendor/conform-qc/index.js +49 -0
  109. package/resolve-advanced/vendor/conform-qc/knowledge/index.js +110 -0
  110. package/resolve-advanced/vendor/conform-qc/ops/conform-core.js +137 -0
  111. package/resolve-advanced/vendor/conform-qc/ops/conform-diff.js +109 -0
  112. package/resolve-advanced/vendor/conform-qc/ops/insert.js +108 -0
  113. package/resolve-advanced/vendor/conform-qc/ops/local-conform.js +61 -0
  114. package/resolve-advanced/vendor/conform-qc/ops/patch.js +80 -0
  115. package/resolve-advanced/vendor/conform-qc/ops/trigger.js +32 -0
  116. package/resolve-advanced/vendor/conform-qc/ops/verify.js +105 -0
  117. package/resolve-advanced/vendor/conform-qc/ops/workflow.js +30 -0
  118. package/resolve-advanced/vendor/conform-qc/oracle/emulate.js +155 -0
  119. package/resolve-advanced/vendor/conform-qc/oracle/index.js +38 -0
  120. package/resolve-advanced/vendor/conform-qc/oracle/resolve.js +196 -0
  121. package/resolve-advanced/vendor/conform-qc/package.json +16 -0
  122. package/resolve-advanced/vendor/conform-qc/packaging/emit-aaf.js +30 -0
  123. package/resolve-advanced/vendor/conform-qc/packaging/emit-fcp7.js +64 -0
  124. package/resolve-advanced/vendor/conform-qc/packaging/index.js +169 -0
  125. package/resolve-advanced/vendor/conform-qc/packaging/media-ops.js +56 -0
  126. package/resolve-advanced/vendor/conform-qc/packaging/otio.js +50 -0
  127. package/resolve-advanced/vendor/conform-qc/packaging/relink.js +50 -0
  128. package/resolve-advanced/vendor/conform-qc/packaging/surgical-relink.js +195 -0
  129. package/resolve-advanced/vendor/conform-qc/parse/index.js +54 -0
  130. package/resolve-advanced/vendor/conform-qc/parse/merge-dto.js +27 -0
  131. package/resolve-advanced/vendor/conform-qc/parse/xmeml-geometry.js +256 -0
  132. package/resolve-advanced/vendor/conform-qc/reference/burnin-ocr.js +100 -0
  133. package/resolve-advanced/vendor/conform-qc/reference/sidecar.js +37 -0
  134. package/resolve-advanced/vendor/conform-qc/repair/analysis.js +62 -0
  135. package/resolve-advanced/vendor/conform-qc/repair/index.js +78 -0
  136. package/resolve-advanced/vendor/conform-qc/repair/media-analysis.js +139 -0
  137. package/resolve-advanced/vendor/conform-qc/repair/media-index.js +135 -0
  138. package/resolve-advanced/vendor/conform-qc/repair/strategies.js +250 -0
  139. package/resolve-advanced/vendor/conform-qc/report/index.js +133 -0
  140. package/resolve-advanced/vendor/conform-qc/roles/index.js +96 -0
  141. package/resolve-advanced/vendor/conform-qc/synthetic/generate.js +233 -0
  142. package/resolve-advanced/vendor/conform-qc/test/advisory.test.js +85 -0
  143. package/resolve-advanced/vendor/conform-qc/test/cli.test.js +39 -0
  144. package/resolve-advanced/vendor/conform-qc/test/compare.test.js +152 -0
  145. package/resolve-advanced/vendor/conform-qc/test/conform-diff.test.js +70 -0
  146. package/resolve-advanced/vendor/conform-qc/test/emulate.test.js +51 -0
  147. package/resolve-advanced/vendor/conform-qc/test/frame-sampler.test.js +30 -0
  148. package/resolve-advanced/vendor/conform-qc/test/geometry.test.js +112 -0
  149. package/resolve-advanced/vendor/conform-qc/test/knowledge.test.js +56 -0
  150. package/resolve-advanced/vendor/conform-qc/test/lifecycle.test.js +110 -0
  151. package/resolve-advanced/vendor/conform-qc/test/local-ffmpeg-sampler.test.js +66 -0
  152. package/resolve-advanced/vendor/conform-qc/test/locate.test.js +63 -0
  153. package/resolve-advanced/vendor/conform-qc/test/media-analysis.test.js +63 -0
  154. package/resolve-advanced/vendor/conform-qc/test/media-index-normalized.test.js +65 -0
  155. package/resolve-advanced/vendor/conform-qc/test/media-index-origin.test.js +53 -0
  156. package/resolve-advanced/vendor/conform-qc/test/metrics-fast.test.js +91 -0
  157. package/resolve-advanced/vendor/conform-qc/test/oracle-reverse.test.js +86 -0
  158. package/resolve-advanced/vendor/conform-qc/test/oracle.test.js +136 -0
  159. package/resolve-advanced/vendor/conform-qc/test/p0-acceptance.test.js +60 -0
  160. package/resolve-advanced/vendor/conform-qc/test/p1-acceptance.test.js +75 -0
  161. package/resolve-advanced/vendor/conform-qc/test/p45-patch-acceptance.test.js +102 -0
  162. package/resolve-advanced/vendor/conform-qc/test/packaging.test.js +133 -0
  163. package/resolve-advanced/vendor/conform-qc/test/parse-stubs.test.js +28 -0
  164. package/resolve-advanced/vendor/conform-qc/test/repair-strategies.test.js +128 -0
  165. package/resolve-advanced/vendor/conform-qc/test/repair.test.js +84 -0
  166. package/resolve-advanced/vendor/conform-qc/test/report.test.js +52 -0
  167. package/resolve-advanced/vendor/conform-qc/test/resolve-readback.test.js +71 -0
  168. package/resolve-advanced/vendor/conform-qc/test/roles.test.js +107 -0
  169. package/resolve-advanced/vendor/conform-qc/test/runbook.test.js +30 -0
  170. package/resolve-advanced/vendor/conform-qc/test/scale-residual.test.js +40 -0
  171. package/resolve-advanced/vendor/conform-qc/test/sidecar.test.js +51 -0
  172. package/resolve-advanced/vendor/conform-qc/test/smoke.test.js +87 -0
  173. package/resolve-advanced/vendor/conform-qc/test/surgical-relink.test.js +73 -0
  174. package/resolve-advanced/vendor/conform-qc/test/synthetic.test.js +98 -0
  175. package/resolve-advanced/vendor/conform-qc/test/verify.test.js +93 -0
  176. package/resolve-advanced/vendor/conform-qc/test/vision-validator.test.js +43 -0
  177. package/resolve-advanced/vendor/conform-qc/test/workflow-driver.test.js +46 -0
  178. package/resolve-advanced/vendor/conform-qc/toolset/index.js +53 -0
  179. package/resolve-advanced/vendor/drp-format/README.md +116 -0
  180. package/resolve-advanced/vendor/drp-format/__tests__/_resolve-verify.js +39 -0
  181. package/resolve-advanced/vendor/drp-format/__tests__/add-media-clip.test.js +30 -0
  182. package/resolve-advanced/vendor/drp-format/__tests__/assemble-timeline.test.js +68 -0
  183. package/resolve-advanced/vendor/drp-format/__tests__/author-project.test.js +57 -0
  184. package/resolve-advanced/vendor/drp-format/__tests__/composition-text.test.js +98 -0
  185. package/resolve-advanced/vendor/drp-format/__tests__/diff.test.js +272 -0
  186. package/resolve-advanced/vendor/drp-format/__tests__/effect-filters-compressed.test.js +143 -0
  187. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/p0-3-session30-empty-effectfilters.drp +0 -0
  188. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-50pct.hex +1 -0
  189. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-dynamic.hex +1 -0
  190. package/resolve-advanced/vendor/drp-format/__tests__/inject-grades.test.js +238 -0
  191. package/resolve-advanced/vendor/drp-format/__tests__/keyed-dict.test.js +85 -0
  192. package/resolve-advanced/vendor/drp-format/__tests__/lut-refs.test.js +119 -0
  193. package/resolve-advanced/vendor/drp-format/__tests__/media-blobs.test.js +25 -0
  194. package/resolve-advanced/vendor/drp-format/__tests__/media-timemap.test.js +119 -0
  195. package/resolve-advanced/vendor/drp-format/__tests__/place-fusion-title.test.js +52 -0
  196. package/resolve-advanced/vendor/drp-format/__tests__/place-generator.test.js +40 -0
  197. package/resolve-advanced/vendor/drp-format/__tests__/place-transition.test.js +48 -0
  198. package/resolve-advanced/vendor/drp-format/__tests__/protobuf-wire.test.js +64 -0
  199. package/resolve-advanced/vendor/drp-format/__tests__/relink-media.test.js +106 -0
  200. package/resolve-advanced/vendor/drp-format/__tests__/splice-clips.test.js +256 -0
  201. package/resolve-advanced/vendor/drp-format/assemble-timeline.js +65 -0
  202. package/resolve-advanced/vendor/drp-format/author-project.js +118 -0
  203. package/resolve-advanced/vendor/drp-format/composition-text.js +213 -0
  204. package/resolve-advanced/vendor/drp-format/curves-encoder.js +763 -0
  205. package/resolve-advanced/vendor/drp-format/diff.js +310 -0
  206. package/resolve-advanced/vendor/drp-format/drp-packager.js +201 -0
  207. package/resolve-advanced/vendor/drp-format/drp-validator.js +809 -0
  208. package/resolve-advanced/vendor/drp-format/effect-encoder.js +640 -0
  209. package/resolve-advanced/vendor/drp-format/extract-lut-refs.js +119 -0
  210. package/resolve-advanced/vendor/drp-format/grade-encoder.example.js +133 -0
  211. package/resolve-advanced/vendor/drp-format/grade-encoder.js +401 -0
  212. package/resolve-advanced/vendor/drp-format/grade-encoder.test.js +171 -0
  213. package/resolve-advanced/vendor/drp-format/grade-encoder.verify.js +249 -0
  214. package/resolve-advanced/vendor/drp-format/grade-node-extractor.js +495 -0
  215. package/resolve-advanced/vendor/drp-format/grade-parameter-decoder.js +774 -0
  216. package/resolve-advanced/vendor/drp-format/index.js +174 -0
  217. package/resolve-advanced/vendor/drp-format/inject-grades.js +219 -0
  218. package/resolve-advanced/vendor/drp-format/keyed-dict.js +227 -0
  219. package/resolve-advanced/vendor/drp-format/marker-encoder.js +532 -0
  220. package/resolve-advanced/vendor/drp-format/media-blobs.js +53 -0
  221. package/resolve-advanced/vendor/drp-format/media-timemap.js +164 -0
  222. package/resolve-advanced/vendor/drp-format/mp-folder-builder.js +293 -0
  223. package/resolve-advanced/vendor/drp-format/node-tree-encoder.js +1246 -0
  224. package/resolve-advanced/vendor/drp-format/package.json +18 -0
  225. package/resolve-advanced/vendor/drp-format/place-fusion-title.js +131 -0
  226. package/resolve-advanced/vendor/drp-format/place-generator.js +77 -0
  227. package/resolve-advanced/vendor/drp-format/place-transition.js +91 -0
  228. package/resolve-advanced/vendor/drp-format/power-window-encoder.js +776 -0
  229. package/resolve-advanced/vendor/drp-format/protobuf-wire.js +111 -0
  230. package/resolve-advanced/vendor/drp-format/qualifier-encoder.js +706 -0
  231. package/resolve-advanced/vendor/drp-format/relink-media.js +218 -0
  232. package/resolve-advanced/vendor/drp-format/rich-title-encoder.js +447 -0
  233. package/resolve-advanced/vendor/drp-format/seq-container-builder.js +572 -0
  234. package/resolve-advanced/vendor/drp-format/seq-surgery.js +155 -0
  235. package/resolve-advanced/vendor/drp-format/splice-clips.js +445 -0
  236. package/resolve-advanced/vendor/drp-format/templates/empty-project.drp +0 -0
  237. package/resolve-advanced/vendor/drp-format/templates/fusion-title.xml +56 -0
  238. package/resolve-advanced/vendor/drp-format/templates/generator-solid-color.xml +21 -0
  239. package/resolve-advanced/vendor/drp-format/templates/media-clip-h264.drp +0 -0
  240. package/resolve-advanced/vendor/drp-format/templates/transition-cross-dissolve.xml +22 -0
  241. package/resolve-advanced/vendor/drp-format/tool-registry.descriptor.js +80 -0
  242. package/resolve-advanced/vendor/drp-format/utils/safe-archive.js +54 -0
  243. package/resolve-advanced/vendor/drp-format/xml-builder.js +759 -0
  244. package/resolve-advanced/vendor/drt-format/__tests__/_resolve-verify.js +21 -0
  245. package/resolve-advanced/vendor/drt-format/__tests__/build.test.js +113 -0
  246. package/resolve-advanced/vendor/drt-format/__tests__/multi-format.test.js +93 -0
  247. package/resolve-advanced/vendor/drt-format/__tests__/parse-real-resolve21.test.js +51 -0
  248. package/resolve-advanced/vendor/drt-format/__tests__/parse.test.js +134 -0
  249. package/resolve-advanced/vendor/drt-format/__tests__/resolve-versions.test.js +120 -0
  250. package/resolve-advanced/vendor/drt-format/__tests__/schema-fingerprint.test.js +62 -0
  251. package/resolve-advanced/vendor/drt-format/__tests__/smoke.test.js +27 -0
  252. package/resolve-advanced/vendor/drt-format/__tests__/validate.test.js +109 -0
  253. package/resolve-advanced/vendor/drt-format/capabilities.js +69 -0
  254. package/resolve-advanced/vendor/drt-format/capability-domains.js +56 -0
  255. package/resolve-advanced/vendor/drt-format/drt-builder.js +42 -0
  256. package/resolve-advanced/vendor/drt-format/drt-parser.js +164 -0
  257. package/resolve-advanced/vendor/drt-format/drt-validator.js +98 -0
  258. package/resolve-advanced/vendor/drt-format/index.js +45 -0
  259. package/resolve-advanced/vendor/drt-format/package.json +17 -0
  260. package/resolve-advanced/vendor/drt-format/resolve-versions.js +153 -0
  261. package/resolve-advanced/vendor/drt-format/schema-fingerprint.js +74 -0
  262. package/resolve-advanced/vendor/drt-format/templates/drt-project-shell.xml +124 -0
  263. package/resolve-advanced/vendor/drx-codec/__tests__/curves-roundtrip.test.js +148 -0
  264. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-face-refinement.drx +58 -0
  265. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-filmgrain.drx +58 -0
  266. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-glow.drx +58 -0
  267. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-lens-flare.drx +58 -0
  268. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-25.drx +58 -0
  269. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-75.drx +58 -0
  270. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-no-lut.drx +58 -0
  271. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-with-lut.drx +58 -0
  272. package/resolve-advanced/vendor/drx-codec/__tests__/hsl-curves-roundtrip.test.js +117 -0
  273. package/resolve-advanced/vendor/drx-codec/__tests__/lut-refs-roundtrip.test.js +112 -0
  274. package/resolve-advanced/vendor/drx-codec/__tests__/matte-roundtrip.test.js +167 -0
  275. package/resolve-advanced/vendor/drx-codec/__tests__/ofx-roundtrip.test.js +215 -0
  276. package/resolve-advanced/vendor/drx-codec/__tests__/ofx-slugs-discovery.test.js +149 -0
  277. package/resolve-advanced/vendor/drx-codec/__tests__/power-window-roundtrip.test.js +230 -0
  278. package/resolve-advanced/vendor/drx-codec/__tests__/qualifier-roundtrip.test.js +226 -0
  279. package/resolve-advanced/vendor/drx-codec/cdl-exporter.js +470 -0
  280. package/resolve-advanced/vendor/drx-codec/color-nlp-parser.js +833 -0
  281. package/resolve-advanced/vendor/drx-codec/data/color-nlp-action-mappings.json +2232 -0
  282. package/resolve-advanced/vendor/drx-codec/drx-generator.js +4123 -0
  283. package/resolve-advanced/vendor/drx-codec/drx-merger.js +320 -0
  284. package/resolve-advanced/vendor/drx-codec/drx-parser.js +1808 -0
  285. package/resolve-advanced/vendor/drx-codec/extract-custom-curves.js +215 -0
  286. package/resolve-advanced/vendor/drx-codec/extract-hsl-curves.js +180 -0
  287. package/resolve-advanced/vendor/drx-codec/extract-lut-refs.js +158 -0
  288. package/resolve-advanced/vendor/drx-codec/extract-matte-finesse.js +83 -0
  289. package/resolve-advanced/vendor/drx-codec/extract-ofx-params.js +226 -0
  290. package/resolve-advanced/vendor/drx-codec/extract-power-window.js +184 -0
  291. package/resolve-advanced/vendor/drx-codec/extract-qualifier.js +99 -0
  292. package/resolve-advanced/vendor/drx-codec/index.js +109 -0
  293. package/resolve-advanced/vendor/drx-codec/node-layout.js +249 -0
  294. package/resolve-advanced/vendor/drx-codec/resolved-drx-generator.js +393 -0
  295. package/resolve-advanced/vendor/drx-codec/tool-resolver.js +974 -0
  296. package/resolve-advanced/vendor/drx-codec/training-examples.js +548 -0
  297. package/resolve-advanced/vendor/drx-codec/vocabulary-intents.js +539 -0
  298. package/resolve-advanced/vendor/drx-parameters/CALIBRATION-STATUS.md +319 -0
  299. package/resolve-advanced/vendor/drx-parameters/DRX-VALUE-SCALING.md +251 -0
  300. package/resolve-advanced/vendor/drx-parameters/calibrated-ranges.json +17737 -0
  301. package/resolve-advanced/vendor/drx-parameters/corrector-types.js +120 -0
  302. package/resolve-advanced/vendor/drx-parameters/index.js +193 -0
  303. package/resolve-advanced/vendor/drx-parameters/parameter-codec.js +451 -0
  304. package/resolve-advanced/vendor/drx-parameters/parameter-ids.js +1292 -0
  305. package/resolve-advanced/vendor/drx-parameters/parameter-ranges.js +570 -0
  306. package/resolve-advanced/vendor/drx-parameters/parameter-validator.js +392 -0
  307. package/resolve-advanced/vendor/drx-parameters/resolvefx-registry.json +14973 -0
  308. package/resolve-advanced/vendor/drx-parameters/tool-registry.descriptor.js +38 -0
  309. package/resolve-advanced/vendor/drx-parameters/tools/drx-analyzer.js +371 -0
  310. package/resolve-advanced/vendor/fairlight/index.js +833 -0
  311. package/resolve-advanced/vendor/fusion-codec/composition-generator.js +401 -0
  312. package/resolve-advanced/vendor/fusion-codec/templates/blur-region.js +103 -0
  313. package/resolve-advanced/vendor/fusion-codec/templates/color-correct.js +87 -0
  314. package/resolve-advanced/vendor/fusion-codec/templates/film-grain.js +106 -0
  315. package/resolve-advanced/vendor/fusion-codec/templates/lower-third.js +241 -0
  316. package/resolve-advanced/vendor/fusion-codec/templates/picture-in-picture.js +188 -0
  317. package/resolve-advanced/vendor/fusion-codec/templates/text-overlay.js +161 -0
  318. package/resolve-advanced/vendor/fusion-codec/templates/title-card.js +184 -0
  319. package/resolve-advanced/vendor/fusion-codec/templates/vignette.js +107 -0
  320. package/resolve-advanced/vendor/fusion-codec/templates/watermark.js +101 -0
  321. package/scripts/agent-rules/README.md +68 -0
  322. package/src/analysis_dashboard.py +233 -1
  323. package/src/granular/common.py +1 -1
  324. package/src/server.py +479 -29
  325. package/src/utils/api_truth.py +117 -0
  326. package/src/utils/cut_ir.py +1 -1
  327. package/src/utils/failure_tracker.py +0 -2
  328. package/src/utils/timeline_xml.py +405 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * P1.5 — extractMatteFinesse tests.
3
+ *
4
+ * No generator emits MATTE_FINESSE params today, so synthesize the
5
+ * parameter list the registry says Resolve emits. All scale: DRX = UI / 100
6
+ * (TRAINED 2026-03-16, corrected 2026-03-22).
7
+ *
8
+ * External matte refs (file path / media pool ID / channel / invert /
9
+ * expand) are a separate encoding not yet reverse-engineered.
10
+ * Queued as deferred Resolve-fixture work — see
11
+ * knowledge/resolve-verifications.md.
12
+ */
13
+
14
+ const test = require('node:test');
15
+ const assert = require('node:assert/strict');
16
+
17
+ const drxParams = require('../../drx-parameters');
18
+ const { extractMatteFinesse, _internals } = require('../extract-matte-finesse');
19
+
20
+ const { MATTE_FINESSE } = drxParams;
21
+ const MF = MATTE_FINESSE;
22
+
23
+ function synthesize(spec) {
24
+ const params = [];
25
+ const add = (id, ui) => params.push({ id, value: ui / 100, name: 'mf' });
26
+ if (spec.denoise !== undefined) add(MF.DENOISE, spec.denoise);
27
+ if (spec.blackClip !== undefined) add(MF.BLACK_CLIP, spec.blackClip);
28
+ if (spec.whiteClip !== undefined) add(MF.WHITE_CLIP, spec.whiteClip);
29
+ if (spec.inOutRatio !== undefined) add(MF.IN_OUT_RATIO, spec.inOutRatio);
30
+ if (spec.cleanBlack !== undefined) add(MF.CLEAN_BLACK, spec.cleanBlack);
31
+ if (spec.cleanWhite !== undefined) add(MF.CLEAN_WHITE, spec.cleanWhite);
32
+ if (spec.morphRadius !== undefined) add(MF.MORPH_RADIUS, spec.morphRadius);
33
+ if (spec.preFilter !== undefined) add(MF.PRE_FILTER, spec.preFilter);
34
+ if (spec.postFilter !== undefined) add(MF.POST_FILTER, spec.postFilter);
35
+ if (spec.shadow !== undefined) add(MF.SHADOW, spec.shadow);
36
+ if (spec.midtone !== undefined) add(MF.MIDTONE, spec.midtone);
37
+ if (spec.highlight !== undefined) add(MF.HIGHLIGHT, spec.highlight);
38
+ return params;
39
+ }
40
+
41
+ const TOL = 1e-3;
42
+ function close(a, b) {
43
+ return Math.abs(a - b) < TOL;
44
+ }
45
+
46
+ test('extractMatteFinesse: denoise-only (training canary: 15 → 0.15)', () => {
47
+ const out = extractMatteFinesse(synthesize({ denoise: 15 }));
48
+ assert.ok(out);
49
+ assert.ok(close(out.denoise, 15));
50
+ });
51
+
52
+ test('extractMatteFinesse: cleanBlack-only (training canary: 30 → 0.30)', () => {
53
+ const out = extractMatteFinesse(synthesize({ cleanBlack: 30 }));
54
+ assert.ok(close(out.cleanBlack, 30));
55
+ });
56
+
57
+ test('extractMatteFinesse: black/white clip pair', () => {
58
+ const out = extractMatteFinesse(synthesize({ blackClip: 10, whiteClip: 90 }));
59
+ assert.ok(close(out.blackClip, 10));
60
+ assert.ok(close(out.whiteClip, 90));
61
+ });
62
+
63
+ test('extractMatteFinesse: zone-based fine tuning (shadow/mid/highlight)', () => {
64
+ const out = extractMatteFinesse(synthesize({
65
+ shadow: 20, midtone: 50, highlight: 80,
66
+ }));
67
+ assert.ok(close(out.shadow, 20));
68
+ assert.ok(close(out.midtone, 50));
69
+ assert.ok(close(out.highlight, 80));
70
+ });
71
+
72
+ test('extractMatteFinesse: filter chain (pre + morph + post)', () => {
73
+ const out = extractMatteFinesse(synthesize({
74
+ preFilter: 5, morphRadius: 3, postFilter: 7,
75
+ }));
76
+ assert.ok(close(out.preFilter, 5));
77
+ assert.ok(close(out.morphRadius, 3));
78
+ assert.ok(close(out.postFilter, 7));
79
+ });
80
+
81
+ test('extractMatteFinesse: full spec round-trips all 12 params', () => {
82
+ const input = {
83
+ denoise: 12, blackClip: 8, whiteClip: 92, inOutRatio: 50,
84
+ cleanBlack: 25, cleanWhite: 35, morphRadius: 4,
85
+ preFilter: 6, postFilter: 10,
86
+ shadow: 22, midtone: 48, highlight: 78,
87
+ };
88
+ const out = extractMatteFinesse(synthesize(input));
89
+ for (const [k, expected] of Object.entries(input)) {
90
+ assert.ok(close(out[k], expected), `${k}: got ${out[k]}, expected ${expected}`);
91
+ }
92
+ });
93
+
94
+ test('extractMatteFinesse: returns null when no matte params present', () => {
95
+ assert.equal(extractMatteFinesse([
96
+ { id: 100663320, value: 0.5, name: 'lift.r' },
97
+ ]), null);
98
+ });
99
+
100
+ test('extractMatteFinesse: handles malformed input', () => {
101
+ assert.equal(extractMatteFinesse(null), null);
102
+ assert.equal(extractMatteFinesse(undefined), null);
103
+ assert.equal(extractMatteFinesse([]), null);
104
+ });
105
+
106
+ test('extractMatteFinesse: handles BigInt values gracefully', () => {
107
+ const out = extractMatteFinesse([
108
+ // BigInt 0 maps to 0 — graceful, not crash.
109
+ { id: MF.DENOISE, value: 0n, name: 'denoise' },
110
+ ]);
111
+ assert.equal(out.denoise, 0);
112
+ });
113
+
114
+ test('extractMatteFinesse: filters non-matte param IDs', () => {
115
+ // 100663320 is LIFT.R, not in MATTE_FINESSE — must be ignored
116
+ const out = extractMatteFinesse([
117
+ { id: MF.DENOISE, value: 0.20, name: 'denoise' },
118
+ { id: 100663320, value: 0.5, name: 'lift.r' },
119
+ ]);
120
+ assert.equal(Object.keys(out).length, 1);
121
+ assert.ok(close(out.denoise, 20));
122
+ });
123
+
124
+ test('internals.drxToUi: round-trips and clamps non-finite', () => {
125
+ assert.equal(_internals.drxToUi(0.15), 15);
126
+ assert.equal(_internals.drxToUi(0.3), 30);
127
+ assert.equal(_internals.drxToUi(NaN), 0);
128
+ assert.equal(_internals.drxToUi(Infinity), 0);
129
+ });
130
+
131
+ // ─── Real round-trip via generateDRX (Session 25 wiring) ─────────────────
132
+
133
+ const { generateDRX } = require('../drx-generator');
134
+ const { parseDRXContent } = require('../drx-parser');
135
+
136
+ async function roundTrip(matteFinesse) {
137
+ const xml = await generateDRX({ matteFinesse }, { label: 'matte-rt' });
138
+ const parsed = await parseDRXContent(xml);
139
+ for (const node of parsed.nodes) {
140
+ for (const corrector of node.correctors) {
141
+ if (corrector.type !== 9) continue;
142
+ const out = extractMatteFinesse(corrector.parameters);
143
+ if (out) return out;
144
+ }
145
+ }
146
+ throw new Error('roundTrip: no Type 9 matte corrector found');
147
+ }
148
+
149
+ test('round-trip: denoise + cleanBlack via real generator', async () => {
150
+ const input = { denoise: 15, cleanBlack: 30 };
151
+ const out = await roundTrip(input);
152
+ assert.ok(close(out.denoise, 15), `denoise: ${out.denoise}`);
153
+ assert.ok(close(out.cleanBlack, 30), `cleanBlack: ${out.cleanBlack}`);
154
+ });
155
+
156
+ test('round-trip: all 12 matte params via real generator', async () => {
157
+ const input = {
158
+ denoise: 12, blackClip: 8, whiteClip: 92, inOutRatio: 50,
159
+ cleanBlack: 25, cleanWhite: 35, morphRadius: 4,
160
+ preFilter: 6, postFilter: 10,
161
+ shadow: 22, midtone: 48, highlight: 78,
162
+ };
163
+ const out = await roundTrip(input);
164
+ for (const [k, v] of Object.entries(input)) {
165
+ assert.ok(close(out[k], v), `${k}: got ${out[k]}, expected ${v}`);
166
+ }
167
+ });
@@ -0,0 +1,215 @@
1
+ /**
2
+ * P1.6 — extractOFXParams tests.
3
+ *
4
+ * Round-trips the F7.F10 OFX tool-list encoding from the generator's
5
+ * buildOFXToolEntry through extractOFXParams / extractOFXTools.
6
+ *
7
+ * Strategy: drive the encoder helpers directly (protoBytes / protoVarint
8
+ * / protoFloat64), assemble the wire bytes the way buildOFXToolEntry
9
+ * does, then verify the decoder recovers pluginId, instanceId, enabled,
10
+ * and each named param.
11
+ *
12
+ * Film Grain coverage at minimum per ledger; other plugins land when
13
+ * P2.1 expands the tool registry.
14
+ */
15
+
16
+ const test = require('node:test');
17
+ const assert = require('node:assert/strict');
18
+
19
+ const { extractOFXParams, extractOFXTools, _internals } = require('../extract-ofx-params');
20
+
21
+ // ─── Mini protobuf encoder helpers (mirror drx-generator.js) ─────────────
22
+
23
+ function encVarint(value) {
24
+ let n = BigInt(value);
25
+ const bytes = [];
26
+ while (n > 0x7fn) {
27
+ bytes.push(Number((n & 0x7fn) | 0x80n));
28
+ n >>= 7n;
29
+ }
30
+ bytes.push(Number(n & 0x7fn));
31
+ return Buffer.from(bytes);
32
+ }
33
+
34
+ function protoVarint(fieldNum, value) {
35
+ const tag = (fieldNum << 3) | 0;
36
+ return Buffer.concat([encVarint(tag), encVarint(value)]);
37
+ }
38
+
39
+ function protoFloat64(fieldNum, value) {
40
+ const tag = (fieldNum << 3) | 1;
41
+ const buf = Buffer.alloc(8);
42
+ buf.writeDoubleLE(value, 0);
43
+ return Buffer.concat([encVarint(tag), buf]);
44
+ }
45
+
46
+ function protoBytes(fieldNum, data) {
47
+ const tag = (fieldNum << 3) | 2;
48
+ return Buffer.concat([encVarint(tag), encVarint(data.length), data]);
49
+ }
50
+
51
+ // Encode one OFX tool's F7.F10 entry (the F1-wrapped form), matching
52
+ // the generator's buildOFXToolEntry F2.F21 container.
53
+ function encodeOFXEntry(pluginId, params, opts = {}) {
54
+ const instanceId = opts.instanceId || 'OfxImageEffectContext_test_abc';
55
+ const enabled = opts.enabled === undefined ? 1 : (opts.enabled ? 1 : 0);
56
+
57
+ const paramEntries = [];
58
+ for (const [name, value] of Object.entries(params)) {
59
+ const nameBuf = Buffer.from(name, 'utf-8');
60
+ let valueBuf;
61
+ if (typeof value === 'string') {
62
+ valueBuf = protoBytes(5, Buffer.from(value, 'utf-8'));
63
+ } else {
64
+ valueBuf = protoFloat64(2, value);
65
+ }
66
+ paramEntries.push(protoBytes(5, Buffer.concat([
67
+ protoBytes(1, nameBuf),
68
+ protoBytes(2, valueBuf),
69
+ ])));
70
+ }
71
+
72
+ const ofxContainer = protoBytes(21, Buffer.concat([
73
+ protoVarint(1, 0x4F4659),
74
+ protoBytes(2, Buffer.from(pluginId, 'utf-8')),
75
+ protoBytes(3, Buffer.from(instanceId, 'utf-8')),
76
+ protoVarint(4, enabled),
77
+ ...paramEntries,
78
+ ]));
79
+
80
+ // Each F10 entry is wrapped in F1 (the F1=2 marker is a separate entry).
81
+ return protoBytes(1, Buffer.concat([
82
+ protoBytes(2, ofxContainer),
83
+ ]));
84
+ }
85
+
86
+ function encodeToolList(entries) {
87
+ return Buffer.concat(entries);
88
+ }
89
+
90
+ // Standard node marker — not an OFX tool, should be ignored by extractor.
91
+ const NODE_MARKER = protoBytes(1, Buffer.concat([
92
+ protoVarint(1, 0xC0000001),
93
+ protoBytes(2, protoVarint(2, 2)),
94
+ ]));
95
+
96
+ test('extractOFXParams: Film Grain plugin round-trips', () => {
97
+ const input = {
98
+ pluginId: 'com.blackmagicdesign.resolvefx.filmgrain',
99
+ params: {
100
+ inMean: 0.5,
101
+ inSize: 1.0,
102
+ inResponse: 'linear',
103
+ },
104
+ };
105
+ const toolList = encodeToolList([
106
+ NODE_MARKER,
107
+ encodeOFXEntry(input.pluginId, input.params),
108
+ ]);
109
+ const out = extractOFXParams(toolList);
110
+ assert.ok(out);
111
+ assert.equal(out.pluginId, input.pluginId);
112
+ assert.ok(out.enabled);
113
+ assert.equal(out.params.inMean, 0.5);
114
+ assert.equal(out.params.inSize, 1.0);
115
+ assert.equal(out.params.inResponse, 'linear');
116
+ });
117
+
118
+ test('extractOFXTools: returns all tools (node marker filtered out)', () => {
119
+ const list = encodeToolList([
120
+ NODE_MARKER,
121
+ encodeOFXEntry('com.blackmagicdesign.resolvefx.filmgrain', { inMean: 0.5 }),
122
+ encodeOFXEntry('com.blackmagicdesign.resolvefx.beauty', { intensity: 0.3 }),
123
+ ]);
124
+ const tools = extractOFXTools(list);
125
+ assert.equal(tools.length, 2);
126
+ assert.equal(tools[0].pluginId, 'com.blackmagicdesign.resolvefx.filmgrain');
127
+ assert.equal(tools[1].pluginId, 'com.blackmagicdesign.resolvefx.beauty');
128
+ });
129
+
130
+ test('extractOFXParams: enabled=false flag round-trips', () => {
131
+ const list = encodeToolList([
132
+ encodeOFXEntry('com.blackmagicdesign.resolvefx.filmgrain', { inMean: 0.5 }, { enabled: false }),
133
+ ]);
134
+ const out = extractOFXParams(list);
135
+ assert.equal(out.enabled, false);
136
+ });
137
+
138
+ test('extractOFXParams: instanceId round-trips', () => {
139
+ const customId = 'OfxImageEffectContext_2026_xyz';
140
+ const list = encodeToolList([
141
+ encodeOFXEntry('com.blackmagicdesign.resolvefx.filmgrain', {},
142
+ { instanceId: customId }),
143
+ ]);
144
+ const out = extractOFXParams(list);
145
+ assert.equal(out.instanceId, customId);
146
+ });
147
+
148
+ test('extractOFXParams: float64 + string params coexist', () => {
149
+ const params = {
150
+ inMean: 0.42,
151
+ inResponse: 'sRGB',
152
+ inSize: 1.5,
153
+ inLayer: 'green',
154
+ };
155
+ const list = encodeToolList([
156
+ encodeOFXEntry('com.blackmagicdesign.resolvefx.filmgrain', params),
157
+ ]);
158
+ const out = extractOFXParams(list);
159
+ for (const [k, v] of Object.entries(params)) {
160
+ assert.equal(out.params[k], v, `${k}: got ${out.params[k]}`);
161
+ }
162
+ });
163
+
164
+ test('extractOFXParams: returns null on empty tool list', () => {
165
+ assert.equal(extractOFXParams(Buffer.alloc(0)), null);
166
+ });
167
+
168
+ test('extractOFXParams: returns null on tool list with only node marker', () => {
169
+ const list = encodeToolList([NODE_MARKER]);
170
+ assert.equal(extractOFXParams(list), null);
171
+ });
172
+
173
+ test('extractOFXParams: rejects buffers without OFY marker', () => {
174
+ const noMarker = protoBytes(1, Buffer.concat([
175
+ protoBytes(2, protoBytes(21, Buffer.concat([
176
+ protoBytes(2, Buffer.from('com.fake', 'utf-8')),
177
+ ]))),
178
+ ]));
179
+ const list = encodeToolList([noMarker]);
180
+ assert.equal(extractOFXParams(list), null);
181
+ });
182
+
183
+ test('extractOFXParams: handles malformed input gracefully', () => {
184
+ assert.equal(extractOFXParams(null), null);
185
+ assert.equal(extractOFXParams(undefined), null);
186
+ assert.equal(extractOFXParams('not a buffer'), null);
187
+ assert.equal(extractOFXParams(42), null);
188
+ });
189
+
190
+ test('extractOFXTools: empty tool list returns []', () => {
191
+ assert.deepEqual(extractOFXTools(Buffer.alloc(0)), []);
192
+ assert.deepEqual(extractOFXTools(null), []);
193
+ });
194
+
195
+ test('internals.OFY_MARKER: matches the registry constant', () => {
196
+ assert.equal(_internals.OFY_MARKER, 0x4F4659);
197
+ });
198
+
199
+ test('internals.decodeParamEntry: float64 + string round-trip', () => {
200
+ const entry = Buffer.concat([
201
+ protoBytes(1, Buffer.from('inMean', 'utf-8')),
202
+ protoBytes(2, protoFloat64(2, 0.5)),
203
+ ]);
204
+ const { name, value } = _internals.decodeParamEntry(entry);
205
+ assert.equal(name, 'inMean');
206
+ assert.equal(value, 0.5);
207
+
208
+ const strEntry = Buffer.concat([
209
+ protoBytes(1, Buffer.from('inResponse', 'utf-8')),
210
+ protoBytes(2, protoBytes(5, Buffer.from('sRGB', 'utf-8'))),
211
+ ]);
212
+ const { name: n2, value: v2 } = _internals.decodeParamEntry(strEntry);
213
+ assert.equal(n2, 'inResponse');
214
+ assert.equal(v2, 'sRGB');
215
+ });
@@ -0,0 +1,149 @@
1
+ /**
2
+ * P2.1 + P2.2 — OFX plugin slug discovery against captured fixtures.
3
+ *
4
+ * Each fixture is a Resolve-exported DRX gallery still with one
5
+ * ResolveFX plugin applied. The test parses the DRX body, locates
6
+ * the F7.F10 tool list, runs extractOFXTools, and asserts the
7
+ * pluginId (slug) matches what RESOLVEFX.SLUGS exposes.
8
+ */
9
+
10
+ const test = require('node:test');
11
+ const assert = require('node:assert/strict');
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+ const zlib = require('node:zlib');
15
+
16
+ const drxParams = require('../../drx-parameters');
17
+ const { extractOFXTools } = require('../extract-ofx-params');
18
+
19
+ const FIXTURES = path.join(__dirname, 'fixtures');
20
+
21
+ // Tiny inline proto walker — locates F1.F7.F10 buffer in the body so
22
+ // extractOFXTools has its expected input shape.
23
+ function readVarint(buf, off) {
24
+ let v = 0n, shift = 0n, b;
25
+ do {
26
+ if (off >= buf.length) throw new Error('truncated varint');
27
+ b = buf[off++];
28
+ v |= BigInt(b & 0x7f) << shift;
29
+ shift += 7n;
30
+ } while (b & 0x80);
31
+ return [v, off];
32
+ }
33
+
34
+ function readFields(buf) {
35
+ const out = [];
36
+ let off = 0;
37
+ while (off < buf.length) {
38
+ let tag;
39
+ try { [tag, off] = readVarint(buf, off); }
40
+ catch (e) { break; }
41
+ const fieldNum = Number(tag >> 3n);
42
+ const wireType = Number(tag & 7n);
43
+ if (wireType === 0) {
44
+ const [v, o2] = readVarint(buf, off);
45
+ out.push({ fieldNum, wireType, value: v });
46
+ off = o2;
47
+ } else if (wireType === 2) {
48
+ const [len, o2] = readVarint(buf, off);
49
+ const L = Number(len);
50
+ out.push({ fieldNum, wireType, value: buf.slice(o2, o2 + L) });
51
+ off = o2 + L;
52
+ } else if (wireType === 1) {
53
+ out.push({ fieldNum, wireType, value: buf.slice(off, off + 8) });
54
+ off += 8;
55
+ } else if (wireType === 5) {
56
+ out.push({ fieldNum, wireType, value: buf.slice(off, off + 4) });
57
+ off += 4;
58
+ } else { break; }
59
+ }
60
+ return out;
61
+ }
62
+
63
+ function findF10Buffer(decompressedBody) {
64
+ const root = readFields(decompressedBody);
65
+ for (const rootField of root) {
66
+ if (rootField.fieldNum !== 1 || rootField.wireType !== 2) continue;
67
+ const nodeBlock = readFields(rootField.value);
68
+ for (const nodeField of nodeBlock) {
69
+ if (nodeField.fieldNum !== 7 || nodeField.wireType !== 2) continue;
70
+ const toolList = readFields(nodeField.value);
71
+ for (const tf of toolList) {
72
+ if (tf.fieldNum === 10 && tf.wireType === 2) return tf.value;
73
+ }
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+
79
+ async function loadOfx(fixtureName) {
80
+ const xml = fs.readFileSync(path.join(FIXTURES, fixtureName), 'utf8');
81
+ const m = xml.match(/<Body>([0-9a-f]+)<\/Body>/);
82
+ if (!m) throw new Error('no Body in fixture');
83
+ const raw = Buffer.from(m[1], 'hex');
84
+ const zstdPayload = raw[0] === 0x81 ? raw.slice(1) : raw;
85
+ // Node 18+ has zstdDecompressSync via node:zlib.
86
+ const decompressed = typeof zlib.zstdDecompressSync === 'function'
87
+ ? zlib.zstdDecompressSync(zstdPayload)
88
+ : await zstdDecompressViaCodec(zstdPayload);
89
+ const f10 = findF10Buffer(decompressed);
90
+ if (!f10) throw new Error('no F7.F10 buffer found');
91
+ return extractOFXTools(f10);
92
+ }
93
+
94
+ async function zstdDecompressViaCodec(payload) {
95
+ const { ZstdCodec } = require('zstd-codec');
96
+ return new Promise((res) => {
97
+ ZstdCodec.run((zstd) => {
98
+ const simple = new zstd.Simple();
99
+ res(Buffer.from(simple.decompress(payload)));
100
+ });
101
+ });
102
+ }
103
+
104
+ // ─── Slug discovery tests ────────────────────────────────────────────────
105
+
106
+ const SLUG_FIXTURES = [
107
+ { fixture: 'p2-1-filmgrain.drx', expectedSlug: drxParams.RESOLVEFX.SLUGS.FILM_GRAIN },
108
+ { fixture: 'p2-1-face-refinement.drx', expectedSlug: drxParams.RESOLVEFX.SLUGS.FACE_REFINEMENT2 },
109
+ { fixture: 'p2-1-lens-flare.drx', expectedSlug: drxParams.RESOLVEFX.SLUGS.LENS_FLARE_V2 },
110
+ { fixture: 'p2-1-glow.drx', expectedSlug: drxParams.RESOLVEFX.SLUGS.GLOW },
111
+ { fixture: 'p2-3-beauty-25.drx', expectedSlug: drxParams.RESOLVEFX.SLUGS.BEAUTY },
112
+ { fixture: 'p2-3-beauty-75.drx', expectedSlug: drxParams.RESOLVEFX.SLUGS.BEAUTY },
113
+ ];
114
+
115
+ for (const { fixture, expectedSlug } of SLUG_FIXTURES) {
116
+ test(`slug discovery: ${fixture} → ${expectedSlug}`, async () => {
117
+ const tools = await loadOfx(fixture);
118
+ assert.equal(tools.length, 1, 'one OFX tool per fixture');
119
+ assert.equal(tools[0].pluginId, expectedSlug);
120
+ assert.equal(tools[0].enabled, true);
121
+ assert.equal(tools[0].marker, 0x4F4659, 'OFY marker present');
122
+ });
123
+ }
124
+
125
+ // ─── Known-params smoke (P2.2 partial coverage) ─────────────────────────
126
+
127
+ test('KNOWN_PARAMS: each slug entry maps to a parameter list', () => {
128
+ for (const slug of Object.values(drxParams.RESOLVEFX.SLUGS)) {
129
+ const params = drxParams.RESOLVEFX.KNOWN_PARAMS[slug];
130
+ assert.ok(Array.isArray(params), `slug "${slug}" has KNOWN_PARAMS`);
131
+ assert.ok(params.length >= 1, `slug "${slug}" has at least one param`);
132
+ assert.ok(params.includes('resolvefxVersion'),
133
+ `slug "${slug}" should include the resolvefxVersion canary param`);
134
+ }
135
+ });
136
+
137
+ // ─── Beauty round-trip canary (P2.3 — proves the encoding side) ─────────
138
+
139
+ test('beauty round-trip: 25 and 75 produce same slug + same param set', async () => {
140
+ const tools25 = await loadOfx('p2-3-beauty-25.drx');
141
+ const tools75 = await loadOfx('p2-3-beauty-75.drx');
142
+ assert.equal(tools25[0].pluginId, tools75[0].pluginId);
143
+ assert.equal(tools25[0].marker, tools75[0].marker);
144
+ // The params keys should match exactly (only values should differ).
145
+ const keys25 = Object.keys(tools25[0].params).sort();
146
+ const keys75 = Object.keys(tools75[0].params).sort();
147
+ assert.deepEqual(keys25, keys75,
148
+ 'same plugin with different param values yields same key set');
149
+ });