rnxsim 0.1.313 → 0.1.314

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 (297) hide show
  1. package/LICENSE +11 -10
  2. package/README.md +5 -0
  3. package/cli/app-config.ts +65 -0
  4. package/cli/app-fonts.ts +408 -0
  5. package/cli/app-project.ts +231 -0
  6. package/cli/app-splash.ts +185 -0
  7. package/cli/app-state-reset.ts +24 -0
  8. package/cli/auth.ts +155 -0
  9. package/cli/bin.ts +594 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2830 -0
  12. package/cli/browser-evals.ts +96 -0
  13. package/cli/commands/agent-wrapper.ts +986 -0
  14. package/cli/commands/agent.ts +423 -0
  15. package/cli/commands/app-fonts.ts +98 -0
  16. package/cli/commands/assert.ts +541 -0
  17. package/cli/commands/auth.ts +59 -0
  18. package/cli/commands/camera.ts +266 -0
  19. package/cli/commands/cleanup.ts +169 -0
  20. package/cli/commands/compat.ts +87 -0
  21. package/cli/commands/config.ts +32 -0
  22. package/cli/commands/control.ts +2142 -0
  23. package/cli/commands/cpu-profile.ts +269 -0
  24. package/cli/commands/daemon-mac-app.ts +169 -0
  25. package/cli/commands/daemon.ts +874 -0
  26. package/cli/commands/debug.ts +719 -0
  27. package/cli/commands/desktop.ts +39 -0
  28. package/cli/commands/detect.ts +197 -0
  29. package/cli/commands/detox.ts +385 -0
  30. package/cli/commands/device.ts +133 -0
  31. package/cli/commands/diagnose.ts +589 -0
  32. package/cli/commands/electron.ts +95 -0
  33. package/cli/commands/film.ts +379 -0
  34. package/cli/commands/flow.ts +1124 -0
  35. package/cli/commands/inspect/actions.ts +622 -0
  36. package/cli/commands/inspect/core.ts +2405 -0
  37. package/cli/commands/inspect/count.ts +17 -0
  38. package/cli/commands/inspect/describe.ts +192 -0
  39. package/cli/commands/inspect/env.ts +23 -0
  40. package/cli/commands/inspect/find.ts +171 -0
  41. package/cli/commands/inspect/get-layout.ts +39 -0
  42. package/cli/commands/inspect/keyboard.ts +52 -0
  43. package/cli/commands/inspect/list.ts +58 -0
  44. package/cli/commands/inspect/memory.ts +215 -0
  45. package/cli/commands/inspect/redaction.ts +39 -0
  46. package/cli/commands/inspect/resolve-target.ts +82 -0
  47. package/cli/commands/inspect/screens.ts +78 -0
  48. package/cli/commands/inspect/settle.ts +22 -0
  49. package/cli/commands/inspect/settling.ts +158 -0
  50. package/cli/commands/inspect/shared.ts +353 -0
  51. package/cli/commands/inspect/sleep.ts +14 -0
  52. package/cli/commands/inspect/tree.ts +32 -0
  53. package/cli/commands/inspect/url.ts +17 -0
  54. package/cli/commands/inspect/wait-event.ts +210 -0
  55. package/cli/commands/inspect/wait-idle.ts +24 -0
  56. package/cli/commands/inspect/wait-ready.ts +74 -0
  57. package/cli/commands/inspect/wait-selector.ts +54 -0
  58. package/cli/commands/inspect/wait.ts +31 -0
  59. package/cli/commands/inspect.ts +4519 -0
  60. package/cli/commands/install-desktop.ts +351 -0
  61. package/cli/commands/login.ts +331 -0
  62. package/cli/commands/logout.ts +31 -0
  63. package/cli/commands/maestro-generate.ts +361 -0
  64. package/cli/commands/maestro.ts +453 -0
  65. package/cli/commands/mode.ts +57 -0
  66. package/cli/commands/no-bridge-hint.ts +80 -0
  67. package/cli/commands/perf.ts +66 -0
  68. package/cli/commands/permissions.ts +203 -0
  69. package/cli/commands/profile.ts +108 -0
  70. package/cli/commands/react.ts +353 -0
  71. package/cli/commands/record.ts +1434 -0
  72. package/cli/commands/report-issue.ts +305 -0
  73. package/cli/commands/reset.ts +85 -0
  74. package/cli/commands/runtime.ts +351 -0
  75. package/cli/commands/screenshot-command.ts +106 -0
  76. package/cli/commands/screenshot-layers.ts +143 -0
  77. package/cli/commands/screenshot-mode.ts +37 -0
  78. package/cli/commands/screenshot.ts +488 -0
  79. package/cli/commands/screenshots-capture.ts +607 -0
  80. package/cli/commands/screenshots.ts +127 -0
  81. package/cli/commands/serve.ts +168 -0
  82. package/cli/commands/setup.ts +545 -0
  83. package/cli/commands/shell-boolean-mode.ts +81 -0
  84. package/cli/commands/skills.ts +467 -0
  85. package/cli/commands/slides.ts +361 -0
  86. package/cli/commands/state.ts +87 -0
  87. package/cli/commands/storage.ts +58 -0
  88. package/cli/commands/telemetry.ts +54 -0
  89. package/cli/commands/three-mode.ts +763 -0
  90. package/cli/commands/timeline.ts +122 -0
  91. package/cli/commands/upgrade.ts +208 -0
  92. package/cli/commands/upload.ts +1225 -0
  93. package/cli/commands/version.ts +54 -0
  94. package/cli/commands/what-happened.ts +327 -0
  95. package/cli/current-sim.ts +204 -0
  96. package/cli/desktop-companion.ts +300 -0
  97. package/cli/drivers/electron.ts +70 -0
  98. package/cli/drivers/index.ts +20 -0
  99. package/cli/drivers/playwright-provisioning.ts +180 -0
  100. package/cli/drivers/playwright.ts +698 -0
  101. package/cli/drivers/registry.ts +65 -0
  102. package/cli/drivers/types.ts +102 -0
  103. package/cli/flow-file.ts +142 -0
  104. package/cli/flow-live-status.ts +120 -0
  105. package/cli/flow-session.ts +187 -0
  106. package/cli/help.ts +80 -0
  107. package/cli/hidden-runtime-alias.ts +19 -0
  108. package/cli/hints.ts +216 -0
  109. package/cli/inspect-notice-state.ts +114 -0
  110. package/cli/maestro-js.ts +334 -0
  111. package/cli/open-url.ts +8 -0
  112. package/cli/parent-pid.ts +204 -0
  113. package/cli/parse-args.ts +211 -0
  114. package/cli/prompt.ts +51 -0
  115. package/cli/recording-access.ts +107 -0
  116. package/cli/registry.ts +1 -0
  117. package/cli/resolve-assets.ts +63 -0
  118. package/cli/run-registry.ts +226 -0
  119. package/cli/runtime-notes.ts +66 -0
  120. package/cli/runtime-summary.ts +25 -0
  121. package/cli/setup-repository.ts +187 -0
  122. package/cli/telemetry.ts +187 -0
  123. package/cli/ws-bridge.ts +798 -0
  124. package/dist-cli/bin.js +5 -5
  125. package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-7YBDCYMA.js} +2 -2
  126. package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-2GHFBHCR.js} +2 -2
  127. package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-RSNVPQSU.js} +2 -2
  128. package/dist-cli/chunks/{assert-54T5SK5F.js → assert-XCMX3XJX.js} +2 -2
  129. package/dist-cli/chunks/{auth-FI5UDI45.js → auth-B442HRAX.js} +2 -2
  130. package/dist-cli/chunks/{beta-JV6UKADW.js → beta-XJ55JK3M.js} +2 -2
  131. package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
  132. package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-277AEQZX.js} +2 -2
  133. package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-2JNSK774.js} +2 -2
  134. package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-32WOTSTR.js} +3 -3
  135. package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-3S753SNQ.js} +2 -2
  136. package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-46ZOLOYA.js} +2 -2
  137. package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-5L7ELDQL.js} +8 -9
  138. package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-7OOPFSQS.js} +2 -2
  139. package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-7SV3RPRW.js} +2 -2
  140. package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-7ZC35MOU.js} +1 -1
  141. package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
  142. package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-APWNH3A4.js} +1 -1
  143. package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
  144. package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-C2NL26TD.js} +1 -1
  145. package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-D2FNUWAB.js} +4 -4
  146. package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-E5T4XSJ3.js} +2 -2
  147. package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-EAC34EQS.js} +1 -1
  148. package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-EG32ML36.js} +2 -2
  149. package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-FXUAC6D5.js} +1 -1
  150. package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
  151. package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-HAXW27SS.js} +2 -2
  152. package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-IZAHPAN6.js} +1 -1
  153. package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-J62KM5TB.js} +2 -2
  154. package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-JEMCD5E4.js} +1 -1
  155. package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-JZS3Q37N.js} +2 -2
  156. package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-KR4JON7D.js} +1 -1
  157. package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
  158. package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-MCWPL644.js} +2 -2
  159. package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
  160. package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-O6TRIZNS.js} +2 -2
  161. package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-P7XL2E73.js} +3 -3
  162. package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-PFQTUKQ4.js} +62 -87
  163. package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-PG5RZCTN.js} +2 -2
  164. package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-QGRI2Z4M.js} +2 -2
  165. package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-QLXXE7GE.js} +1 -1
  166. package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-QOJJJWJE.js} +89 -133
  167. package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-RWZY5427.js} +2 -2
  168. package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RZKU2K3J.js} +2 -2
  169. package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-SBV4IK4H.js} +1 -1
  170. package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-SGMVFFMK.js} +1 -1
  171. package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-TAX4UT2N.js} +1 -1
  172. package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-TUOFAWXT.js} +1 -1
  173. package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-UHZLOHGP.js} +1 -1
  174. package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-VC7V76U3.js} +1 -1
  175. package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-VQVMLW4U.js} +1 -1
  176. package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-VUKKYPZN.js} +2 -2
  177. package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-WGGRJDRE.js} +1 -1
  178. package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
  179. package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
  180. package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-XULEACM4.js} +2 -2
  181. package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-YFSDM7AX.js} +4 -4
  182. package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
  183. package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-ZBSJO4NB.js} +10 -9
  184. package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-EYLGCA6Z.js} +2 -2
  185. package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
  186. package/dist-cli/chunks/{compat-ZD65FED3.js → compat-TLJYHB4E.js} +2 -2
  187. package/dist-cli/chunks/{config-XMJRNM2A.js → config-4JWUOEXK.js} +2 -2
  188. package/dist-cli/chunks/{control-KMIQT3QP.js → control-HPAOYF4N.js} +2 -2
  189. package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
  190. package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-4BKXF6KI.js} +5 -5
  191. package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-ZQ6ZD2S6.js} +3 -3
  192. package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-WNPASSS3.js} +2 -2
  193. package/dist-cli/chunks/{device-XBNDSB2R.js → device-BXLXVG3V.js} +2 -2
  194. package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-3QX7W5RW.js} +2 -2
  195. package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KSWKI7WB.js} +2 -2
  196. package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
  197. package/dist-cli/chunks/{film-BJGTBYZB.js → film-GDMR33VO.js} +3 -3
  198. package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
  199. package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
  200. package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
  201. package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
  202. package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
  203. package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
  204. package/dist-cli/chunks/{login-FJ737MWG.js → login-IJAPUZHI.js} +4 -4
  205. package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-QMXDFU2X.js} +2 -2
  206. package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-ZXU3YCVX.js} +3 -3
  207. package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-PYB5QY7K.js} +3 -3
  208. package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-WGUCL5FZ.js} +2 -2
  209. package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-WH2O6H36.js} +2 -2
  210. package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-TOD3UFAH.js} +2 -2
  211. package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-I5BRJGTB.js} +2 -2
  212. package/dist-cli/chunks/{record-QPPC2S4E.js → record-ZYL2FYSK.js} +3 -3
  213. package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-TAI6DYZO.js} +2 -2
  214. package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
  215. package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
  216. package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7ANLODZY.js} +7 -7
  217. package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-A7FYXSVU.js} +3 -3
  218. package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-WT2ZY6CB.js} +2 -2
  219. package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
  220. package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-PCWLD22V.js} +2 -2
  221. package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-27ZHWCQS.js} +2 -2
  222. package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
  223. package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-OYC57C4X.js} +6 -6
  224. package/dist-cli/chunks/store-32HSPZHI.js +4 -0
  225. package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
  226. package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-RAJJFQT6.js} +2 -2
  227. package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-OEFNZRIP.js} +2 -2
  228. package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
  229. package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
  230. package/dist-cli/chunks/{web-DG3WBYD3.js → web-XBUTBVGR.js} +2 -2
  231. package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-YNHRFUDX.js} +3 -3
  232. package/dist-lib/agent-daemon-client.cjs +1 -1
  233. package/dist-lib/agent-events.cjs +1 -1
  234. package/dist-lib/agent-identity.cjs +1 -1
  235. package/dist-lib/agent-sessions.cjs +1 -1
  236. package/dist-lib/attached-projects.cjs +1 -1
  237. package/dist-lib/auth/shared-session.cjs +1 -1
  238. package/dist-lib/backend-origin.cjs +1 -1
  239. package/dist-lib/beta.cjs +1 -1
  240. package/dist-lib/beta.mjs +1 -1
  241. package/dist-lib/bridge-constants.cjs +1 -1
  242. package/dist-lib/bridge-contract.cjs +20 -0
  243. package/dist-lib/cli-constants.cjs +1 -1
  244. package/dist-lib/config.cjs +1 -1
  245. package/dist-lib/detox/index.cjs +1 -1
  246. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  247. package/dist-lib/home-paths.cjs +67 -28
  248. package/dist-lib/host/bridge-host.cjs +140 -12
  249. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  250. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  251. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  252. package/dist-lib/host/websocket-proxy.cjs +1 -1
  253. package/dist-lib/index.cjs +2815 -40
  254. package/dist-lib/jump-to-source-babel.cjs +1 -1
  255. package/dist-lib/menu.cjs +1 -1
  256. package/dist-lib/menu.mjs +1 -1
  257. package/dist-lib/metro.cjs +1 -1
  258. package/dist-lib/profiles.cjs +1 -1
  259. package/dist-lib/public-brand.cjs +1 -1
  260. package/dist-lib/render-mode.cjs +1 -1
  261. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  262. package/dist-lib/sdk.cjs +2549 -2061
  263. package/dist-lib/sdk.mjs +2543 -2061
  264. package/dist-lib/skills.cjs +480 -280
  265. package/dist-lib/vite.cjs +1 -1
  266. package/package.json +8 -2
  267. package/src/bridge-constants.ts +3 -4
  268. package/src/bridge-contract.ts +251 -0
  269. package/src/connect.ts +83 -0
  270. package/src/disk-cleanup.ts +30 -0
  271. package/src/home-paths.ts +81 -38
  272. package/src/host/bridge-host.ts +134 -6
  273. package/src/index.ts +27 -1
  274. package/src/sdk.ts +8 -0
  275. package/src/sim-client.ts +660 -0
  276. package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
  277. package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
  278. package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
  279. package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
  280. package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
  281. package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
  282. package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
  283. package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
  284. package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
  285. package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
  286. package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
  287. package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
  288. package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
  289. package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
  290. package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
  291. package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
  292. package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
  293. package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
  294. package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
  295. package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
  296. package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
  297. package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
@@ -0,0 +1,379 @@
1
+ // sootsim film — record a flow on the flat canvas, then re-play that
2
+ // recording on the 3d device stage under a camera cinematic and capture the
3
+ // stage as video.
4
+ //
5
+ // running a flow inside live 3d mode is too slow to look good (the guest
6
+ // app, the shell, and the WebGL stage all compete for the same cores), so
7
+ // filming is two-phase: the flow records at full speed on the flat canvas
8
+ // via the engine recording store, then the 3d recording editor plays the
9
+ // webm back on the phone screen while only the stage renders. the stage
10
+ // canvas is captured with MediaRecorder (WebGL content only — the editor's
11
+ // DOM dock never appears in the capture).
12
+
13
+ import { spawnSync } from 'node:child_process'
14
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs'
15
+ import { homedir } from 'node:os'
16
+ import { dirname, extname, join, resolve } from 'node:path'
17
+ import {
18
+ callInBridgeWrite,
19
+ createBridgeFromParsed,
20
+ parseBridgeCliArgs,
21
+ type WsBridge,
22
+ } from '../ws-bridge'
23
+ import { buildConfigureOptions, waitForThreeModeRuntimeReadyOnBridge } from './three-mode'
24
+
25
+ interface FilmOptions {
26
+ port?: number
27
+ verbose?: boolean
28
+ }
29
+
30
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
31
+
32
+ // flags consumed by film itself; everything else that buildConfigureOptions
33
+ // understands (--background, --colorway, --env-*, --room, --focus*, ...)
34
+ // passes through to the stage configure call.
35
+ const FILM_VALUE_FLAGS = ['--out', '--fps', '--script', '--slow']
36
+ const FILM_BOOLEAN_FLAGS = ['--keep-three']
37
+
38
+ const CONFIGURE_VALUE_FLAGS = [
39
+ '--background',
40
+ '--gradient',
41
+ '--gradient-angle',
42
+ '--colorway',
43
+ '--environment',
44
+ '--env-shape',
45
+ '--env-spread',
46
+ '--env-size',
47
+ '--env-color',
48
+ '--env-glow',
49
+ '--env-plastic',
50
+ '--room',
51
+ '--focus',
52
+ '--focus-amount',
53
+ '--focus-range',
54
+ ]
55
+
56
+ const MAX_FILM_MS = 120_000
57
+ const STAGE_BITRATE = 16_000_000
58
+
59
+ function flagValue(args: string[], flag: string): string | undefined {
60
+ const i = args.indexOf(flag)
61
+ return i >= 0 ? args[i + 1] : undefined
62
+ }
63
+
64
+ async function evalInPage<T>(bridge: WsBridge, code: string, timeoutMs = 30_000) {
65
+ return (await bridge.send({ type: 'evaluate', code }, { timeoutMs })) as T
66
+ }
67
+
68
+ export async function runFilm(args: string[], opts: FilmOptions): Promise<number> {
69
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
70
+ console.log(`
71
+ rnxsim film — record a flow flat, replay it on the 3d stage, save a clip
72
+
73
+ usage:
74
+ rnxsim film <flow.yaml> [options]
75
+
76
+ how it works:
77
+ 1. the flow runs at full speed on the flat canvas while the engine
78
+ recording store captures the device screen
79
+ 2. 3d mode + the recording editor come up and play that capture back on
80
+ the phone screen while a camera script animates
81
+ 3. the WebGL stage canvas is recorded and saved
82
+
83
+ options:
84
+ --out <path> output file (.webm, or .mp4 when ffmpeg is
85
+ installed). default: ~/Desktop/sootsim-films/<flow>.webm
86
+ --script <id> camera script to animate during playback
87
+ (default: hero-arc)
88
+ --fps <n> stage capture framerate (default: 30)
89
+ --slow <ms> per-step delay forwarded to the flow runner
90
+ --keep-three leave 3d mode + the recording editor on afterwards
91
+ --sim <id> target a specific connected sim
92
+
93
+ stage decor (same flags as \`rnxsim mode three configure\`):
94
+ --background <id> · --gradient <stops> · --gradient-angle <deg>
95
+ --colorway <id> · --room on|off · --focus on|off · --focus-amount <id>
96
+ --focus-range <id> · --environment on|off · --env-shape/spread/size/color
97
+ --env-glow on|off · --env-plastic on|off
98
+
99
+ examples:
100
+ rnxsim film flows/feed-scroll.yaml
101
+ rnxsim film flows/swap.yaml --script hero-arc --background gradient-tide --out ~/Desktop/swap-3d.mp4
102
+ rnxsim film flows/login.yaml --colorway deep-blue --environment on --env-shape pane --room on
103
+ `)
104
+ return 0
105
+ }
106
+
107
+ const flowPath = args.find((a) => !a.startsWith('--') && /\.ya?ml$/.test(a))
108
+ if (!flowPath || !existsSync(resolve(flowPath))) {
109
+ console.error(` film: flow file not found: ${flowPath ?? '(none given)'}`)
110
+ return 1
111
+ }
112
+
113
+ const fps = Number.parseInt(flagValue(args, '--fps') ?? '30', 10)
114
+ const scriptId = flagValue(args, '--script') ?? 'hero-arc'
115
+ const keepThree = args.includes('--keep-three')
116
+ const flowName = flowPath.replace(/^.*\//, '').replace(/\.ya?ml$/, '')
117
+ const outArg = flagValue(args, '--out')
118
+ const outPath = resolve(
119
+ outArg ?? join(homedir(), 'Desktop', 'sootsim-films', `${flowName}.webm`),
120
+ )
121
+ const wantsMp4 = extname(outPath).toLowerCase() === '.mp4'
122
+ if (wantsMp4 && spawnSync('ffmpeg', ['-version']).status !== 0) {
123
+ console.error(' film: --out ends in .mp4 but ffmpeg is not installed')
124
+ console.error(' install ffmpeg or use a .webm output path')
125
+ return 1
126
+ }
127
+
128
+ const parsed = parseBridgeCliArgs(args, {
129
+ port: opts.port,
130
+ stripValueFlags: [...FILM_VALUE_FLAGS, ...CONFIGURE_VALUE_FLAGS],
131
+ stripBooleanFlags: FILM_BOOLEAN_FLAGS,
132
+ })
133
+ const bridge = createBridgeFromParsed({ ...parsed, commandTimeoutMs: 60_000 })
134
+
135
+ try {
136
+ // remember the shell state we mutate so a plain run is side-effect free
137
+ const before = await evalInPage<{ threeMode?: boolean; editor?: boolean }>(
138
+ bridge,
139
+ `(() => { const s = window.SootSim?.bridges?.settings?.get?.() || {}
140
+ return { threeMode: s.threeMode === true, editor: s.threeRecordingEditor === true } })()`,
141
+ )
142
+
143
+ // -- phase 1: record the flow on the flat canvas ----------------------
144
+ console.log(` film: recording flow ${flowName} on the flat canvas`)
145
+ const started = await evalInPage<boolean>(
146
+ bridge,
147
+ `(async () => !!(await window.SootSim?.bridges?.startRecording?.('video', undefined, { skipEntitlement: true })))()`,
148
+ )
149
+ if (!started) {
150
+ console.error(
151
+ ' film: engine recording store did not start (is @sootsim/plugin-recording installed?)',
152
+ )
153
+ return 1
154
+ }
155
+ const t0 = Date.now()
156
+ await sleep(500)
157
+
158
+ const { runFlowPlayback } = await import('./flow')
159
+ const playbackArgs = [flowPath]
160
+ const simFlag = flagValue(args, '--sim')
161
+ if (simFlag) playbackArgs.push('--sim', simFlag)
162
+ const slowFlag = flagValue(args, '--slow')
163
+ if (slowFlag) playbackArgs.push('--slow', slowFlag)
164
+ const flowCode = await runFlowPlayback(playbackArgs)
165
+ await sleep(400)
166
+ const durationMs = Math.min(Date.now() - t0, MAX_FILM_MS)
167
+ await evalInPage(bridge, `window.SootSim?.bridges?.stopRecording?.()`)
168
+ if (flowCode !== 0) {
169
+ console.error(` film: flow failed (exit ${flowCode}) — not filming a broken take`)
170
+ return flowCode
171
+ }
172
+
173
+ let hasBlob = false
174
+ for (let i = 0; i < 50; i++) {
175
+ await sleep(400)
176
+ const st = await evalInPage<{ state?: string; hasBlob?: boolean }>(
177
+ bridge,
178
+ `(() => { const s = window.SootSim?.bridges?.getRecordingState?.()
179
+ return { state: s?.state, hasBlob: !!s?.lastVideoBlob } })()`,
180
+ )
181
+ if (st?.state === 'idle' && st.hasBlob) {
182
+ hasBlob = true
183
+ break
184
+ }
185
+ }
186
+ if (!hasBlob) {
187
+ console.error(' film: flow recording never produced a video blob')
188
+ return 1
189
+ }
190
+ console.log(` film: flow captured (${(durationMs / 1000).toFixed(1)}s)`)
191
+
192
+ // -- phase 2: bring up the 3d stage + recording editor -----------------
193
+ await evalInPage(
194
+ bridge,
195
+ `window.SootSim?.bridges?.settings?.set?.('threeMode', true)`,
196
+ )
197
+ await waitForThreeModeRuntimeReadyOnBridge(bridge)
198
+ await sleep(1400)
199
+ await evalInPage(
200
+ bridge,
201
+ `window.SootSim?.bridges?.settings?.set?.('threeRecordingEditor', true)`,
202
+ )
203
+ let replaySeeded = false
204
+ for (let i = 0; i < 60; i++) {
205
+ await sleep(250)
206
+ const seeded = await evalInPage<boolean>(
207
+ bridge,
208
+ `(() => { const rs = window.SootSim?.replayStore
209
+ return rs?.value?.preview?.surface === 'video' && !!rs?.value?.preview?.flowVideoUrl })()`,
210
+ )
211
+ if (seeded) {
212
+ replaySeeded = true
213
+ break
214
+ }
215
+ }
216
+ if (!replaySeeded) {
217
+ console.error(' film: recording editor did not seed the replay store')
218
+ return 1
219
+ }
220
+
221
+ const configure = buildConfigureOptions(args)
222
+ delete configure.script
223
+ configure.resetPose = true
224
+ await callInBridgeWrite(bridge, 'SootSim.bridges.threeMode.configure', configure)
225
+ await sleep(2400)
226
+
227
+ // -- phase 3: capture the stage while the replay + camera script run ---
228
+ console.log(` film: filming the 3d stage (script: ${scriptId})`)
229
+ const rec = await evalInPage<{ ok: boolean; error?: string }>(
230
+ bridge,
231
+ `(() => {
232
+ const c = window.SootSim?.bridges?.threeMode?.getCanvas?.()
233
+ if (!c || !c.captureStream) return { ok: false, error: 'no stage canvas' }
234
+ let mime = 'video/webm;codecs=vp9'
235
+ if (!('MediaRecorder' in window) || !MediaRecorder.isTypeSupported(mime)) mime = 'video/webm'
236
+ const stream = c.captureStream(${fps})
237
+ const mr = new MediaRecorder(stream, { mimeType: mime, videoBitsPerSecond: ${STAGE_BITRATE} })
238
+ const st = { mr, chunks: [], b64: null }
239
+ mr.ondataavailable = (e) => { if (e.data && e.data.size) st.chunks.push(e.data) }
240
+ mr.onstop = () => {
241
+ const blob = new Blob(st.chunks, { type: 'video/webm' })
242
+ const fr = new FileReader()
243
+ fr.onload = () => { st.b64 = String(fr.result).split(',')[1] || '' }
244
+ fr.readAsDataURL(blob)
245
+ }
246
+ window.__sootsimFilm = st
247
+ mr.start(200)
248
+ return { ok: true }
249
+ })()`,
250
+ )
251
+ if (!rec?.ok) {
252
+ console.error(` film: stage capture failed: ${rec?.error ?? 'unknown'}`)
253
+ return 1
254
+ }
255
+
256
+ await evalInPage(
257
+ bridge,
258
+ `((dur) => {
259
+ const rs = window.SootSim?.replayStore
260
+ if (!rs) return false
261
+ const probed = rs.value?.playback?.durationMs
262
+ const total = probed && probed > 500 ? probed : dur
263
+ const start = performance.now()
264
+ const tick = () => {
265
+ const t = performance.now() - start
266
+ rs.emit({ ...rs.value, playback: { ...rs.value.playback, playing: true, scrubbing: false, currentTimeMs: Math.min(t, total) } })
267
+ if (t < total) requestAnimationFrame(tick)
268
+ }
269
+ requestAnimationFrame(tick)
270
+ return true
271
+ })(${durationMs})`,
272
+ )
273
+ await callInBridgeWrite(bridge, 'SootSim.bridges.threeMode.configure', {
274
+ script: { id: scriptId, progress: 1, animate: true, slow: true },
275
+ })
276
+ await sleep(durationMs + 700)
277
+
278
+ await evalInPage(
279
+ bridge,
280
+ `(() => { const s = window.__sootsimFilm; if (s?.mr?.state === 'recording') s.mr.stop(); return true })()`,
281
+ )
282
+ let b64Size = 0
283
+ for (let i = 0; i < 60; i++) {
284
+ await sleep(500)
285
+ const probe = await evalInPage<{ ready: boolean; size: number }>(
286
+ bridge,
287
+ `(() => { const s = window.__sootsimFilm; return { ready: !!(s && s.b64), size: s && s.b64 ? s.b64.length : 0 } })()`,
288
+ )
289
+ if (probe?.ready && probe.size > 0) {
290
+ b64Size = probe.size
291
+ break
292
+ }
293
+ }
294
+ if (!b64Size) {
295
+ console.error(' film: stage recording never finished encoding')
296
+ return 1
297
+ }
298
+
299
+ // stream the base64 back in chunks (bridge messages cap out on very
300
+ // large single payloads)
301
+ const CHUNK = 2_400_000
302
+ let b64 = ''
303
+ for (let offset = 0; offset < b64Size; offset += CHUNK) {
304
+ const part = await evalInPage<{ data: string }>(
305
+ bridge,
306
+ `(() => ({ data: window.__sootsimFilm.b64.substr(${offset}, ${CHUNK}) }))()`,
307
+ )
308
+ b64 += part.data
309
+ }
310
+ await evalInPage(
311
+ bridge,
312
+ `(() => { try { delete window.__sootsimFilm } catch {} return true })()`,
313
+ )
314
+
315
+ const webmBytes = Buffer.from(b64, 'base64')
316
+ mkdirSync(dirname(outPath), { recursive: true })
317
+ if (wantsMp4) {
318
+ const tmpWebm = `${outPath}.tmp.webm`
319
+ writeFileSync(tmpWebm, webmBytes)
320
+ const ff = spawnSync(
321
+ 'ffmpeg',
322
+ [
323
+ '-y',
324
+ '-i',
325
+ tmpWebm,
326
+ '-c:v',
327
+ 'libx264',
328
+ // libx264 + yuv420p requires even dimensions; the stage canvas is
329
+ // whatever the window layout produced (e.g. 750x987)
330
+ '-vf',
331
+ 'scale=trunc(iw/2)*2:trunc(ih/2)*2',
332
+ '-pix_fmt',
333
+ 'yuv420p',
334
+ '-movflags',
335
+ '+faststart',
336
+ outPath,
337
+ ],
338
+ { stdio: ['ignore', 'ignore', 'pipe'] },
339
+ )
340
+ if (ff.status !== 0) {
341
+ // keep the source webm so the capture isn't lost
342
+ const tail = String(ff.stderr ?? '')
343
+ .split('\n')
344
+ .filter(Boolean)
345
+ .slice(-4)
346
+ .join('\n ')
347
+ console.error(` film: ffmpeg transcode failed:\n ${tail}`)
348
+ console.error(` film: source kept at ${tmpWebm}`)
349
+ return 1
350
+ }
351
+ unlinkSync(tmpWebm)
352
+ } else {
353
+ writeFileSync(outPath, webmBytes)
354
+ }
355
+
356
+ // -- restore shell state ----------------------------------------------
357
+ if (!keepThree) {
358
+ if (!before?.editor) {
359
+ await evalInPage(
360
+ bridge,
361
+ `window.SootSim?.bridges?.settings?.set?.('threeRecordingEditor', false)`,
362
+ )
363
+ }
364
+ if (!before?.threeMode) {
365
+ await evalInPage(
366
+ bridge,
367
+ `window.SootSim?.bridges?.settings?.set?.('threeMode', false)`,
368
+ )
369
+ }
370
+ }
371
+
372
+ console.log(
373
+ ` film: saved ${outPath} (${(webmBytes.length / 1e6).toFixed(1)}MB source)`,
374
+ )
375
+ return 0
376
+ } finally {
377
+ bridge.close()
378
+ }
379
+ }