@dailies-studio/shot-editor 0.1.0 → 0.2.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 (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,730 @@
1
+ // The playback render scale's controller (the playback plan's Phase 5): the
2
+ // share of the display's density a played frame renders at, lowered only while
3
+ // played frames miss their deadline and the GPU's measured time says the
4
+ // drawing is why. The stage hands it to the runtime as
5
+ // `ShotRuntimeProps.presentationScale`, whose child asks it once per played
6
+ // frame and draws the frame at that scale into an offscreen target, never
7
+ // resizing the canvas (packages/shot/src/presentationScale.ts).
8
+ //
9
+ // ── A REAL PROBLEM, AND THE GPU'S ──────────────────────────────────────────
10
+ //
11
+ // Two inputs, and a step down needs both:
12
+ //
13
+ // - THE DEADLINES. Every frame of the transport that reaches the canvas, as
14
+ // the stage's telemetry matches the runtime's echo to its publication
15
+ // (`presented`), timed by the clock read its request took before it was
16
+ // drawn: a gap between two frames is the first one's draw and the wait for
17
+ // the next refresh, so a frame drawn slower than the one before, in time
18
+ // for its refresh, is not late. A frame is late when it follows the one
19
+ // before by more than its cadence holds a frame on a 60 Hz display, plus
20
+ // half a refresh (`deadlineMs`), or when a refresh went by without a frame
21
+ // and it skipped a document frame (`MISSED_REFRESH_MS`): the transport runs
22
+ // on wall time, so a stage that cannot keep up skips frames rather than
23
+ // showing them late, and at 24, 25 and 50 fps a stage held to 20, 20 and 30
24
+ // fps shows only gaps the cadence shows too. A late frame counts once,
25
+ // however late, and a window has a problem only when `LATE_SHARE` of its
26
+ // frames came late: one long stall is one late frame, where an fps average
27
+ // reads it as many.
28
+ // - THE GPU'S TIME. three's timestamp queries time the render passes and
29
+ // nothing outside them (gpuTimer.ts). A frame missed because a texture
30
+ // uploaded, a pipeline compiled, a raster was cut or the main thread was
31
+ // busy costs the same at any resolution, and none of that is in the GPU's
32
+ // time, so such a film misses deadlines with its GPU time under the budget
33
+ // and never steps. A GPU whose clocks fell with such a film's load can read
34
+ // over it (cero's on the integrated GPU did): then the step's own verdict
35
+ // undoes it ("A STEP DOWN MUST PAY").
36
+ //
37
+ // Neither alone. GPU timestamps follow the GPU's clocks, and a lightly loaded
38
+ // GPU lowers its clocks, so its frame's milliseconds rise toward the period
39
+ // while it still has headroom (the RTX 3060 Laptop's graphics clock ranged
40
+ // 210 to 1965 MHz within single runs). Stepping on GPU time alone, the
41
+ // controller stepped down with every deadline met, and judged a step's relief
42
+ // by clocks coming back.
43
+ //
44
+ // ── THE RULES ──────────────────────────────────────────────────────────────
45
+ //
46
+ // - The budget is the document's frame period, and the steps are 1, 0.85, 0.7
47
+ // and 0.5 of the display's density (`RENDER_SCALE_STEPS`).
48
+ // - A window is the document's frame rate's worth of GPU samples, or a second
49
+ // of them once it holds `MIN_WINDOW_SAMPLES`, whichever closes it first:
50
+ // frames dropped to a GPU that is behind would otherwise stretch a window
51
+ // over several seconds, and three's WebGL timer loses the queries of the
52
+ // frames drawn while a resolve is out, so there it sees one frame in two or
53
+ // three. Its deadlines are the frames presented while it gathered them. With
54
+ // fewer than `MIN_WINDOW_SAMPLES` of those it says nothing of deadlines: it
55
+ // steps nothing down and judges no step.
56
+ // - A WINDOW IS ONE SHOT'S (`PresentationFrame.shot`). A frame of another
57
+ // Shot ends the window in progress, judged if it holds `MIN_WINDOW_SAMPLES`
58
+ // and dropped if not, and the `SAMPLE_LAG_FRAMES` samples after it are
59
+ // dropped too, the old Shot's last frames' that land after its ask. No gap
60
+ // ends or starts at a cut's frame (`PresentationFrame.cut`): the one ending
61
+ // there follows another picture, or a jump of the transport (a loop's wrap,
62
+ // a seek), and the one starting there is the cut's own draw, its compiles
63
+ // and uploads, which no resolution buys back. Two Shots draw two pictures,
64
+ // so a p75 of both describes neither, and a step is judged by its own
65
+ // Shot's windows ("A STEP DOWN MUST PAY").
66
+ // - A window whose frames miss their deadline while its GPU p75 is over
67
+ // `DOWN_SHARE` of the period steps DOWN, predictively: to the largest step
68
+ // s' predicted to fit under it. A p75 over the whole period is a severe miss
69
+ // and lands at the next frame; every other step lands at a cut, because
70
+ // pixel-sized post effects the chain cannot keep at the canvas's size (the
71
+ // tilt-shift spread, the bloom chain) change their look with the scale. A
72
+ // later window that steps nothing down, its deadlines met or its GPU inside
73
+ // the budget, withdraws a step still waiting for its cut.
74
+ // - `UP_WINDOWS` windows in a row whose prediction puts the step above under
75
+ // `UP_SHARE` of the period step UP one, at a cut, late frames or not: a film
76
+ // late for its main thread is made no later by pixels its GPU has room for.
77
+ // The gap between the two shares is the hysteresis. Under DVFS the rule can
78
+ // only err toward staying: below a step, a GPU with headroom lowers its
79
+ // clocks, so its time there overstates what the step above costs once its
80
+ // clocks follow the load up. And a step up that puts the GPU's time over the
81
+ // budget with every deadline met stays, where on GPU time alone it stepped
82
+ // back down, and climbed again four windows later.
83
+ // - THE PREDICTION. A frame below 1 is its own passes, which shrink with the
84
+ // area, and one pass up to the canvas, which covers the canvas whatever the
85
+ // scale (gpuTimer.ts times it apart): the present, three's output pass drawn
86
+ // at the canvas's density, for a Shot without post, or the copy of P for one
87
+ // with. So a step s' costs the window's content p75 times (s'/s)^2, plus the
88
+ // present's p75 at every scale, plus the copy's p75 when s' is below 1.
89
+ // Folded into the per-pixel term instead, the copy priced every step up too
90
+ // dear, and a film that fits at full density could stay below it for good.
91
+ // Priced as a copy, the present left a step up to 1 a canvas pass too cheap:
92
+ // a film whose output pass costs more than the hysteresis stepped up into a
93
+ // window over the budget, and down again, every few windows. A frame at 1
94
+ // draws the present's pass as three's own output pass, untagged, and a Shot
95
+ // with post draws none, so the two are each Shot's own, from its last window
96
+ // below 1 (`CanvasPasses`), and a window at 1 takes its Shot's present out of
97
+ // its time before the time scales. Counted in the per-pixel term and again
98
+ // whole, the output pass priced every step down from 1 too dear and chose a
99
+ // step lower than the film needed. A Shot that has not drawn below 1 is
100
+ // priced with the last window's passes below 1, another Shot's, which may be
101
+ // the other kind; before any, with none, which errs toward the higher step,
102
+ // and the next window takes another. The s^2 model is conservative upward
103
+ // besides: a pass's fixed cost does not shrink with the area, so a
104
+ // measurement below a step overstates it.
105
+ // - The window after every step is not judged: the timer's samples land one
106
+ // to three frames late, and the first frames at a new size allocate.
107
+ // - A STEP DOWN MUST PAY, in deadlines. Each Shot's step down is on trial
108
+ // until the first window of that Shot at the step's scale or below judges
109
+ // it (`#trials`). A window of another Shot says nothing of the step, and a
110
+ // step that waits for a cut is followed by the next Shot's windows: compared
111
+ // with them, a heavier Shot read as no relief, and a lighter one as relief.
112
+ // Another Shot's step down meanwhile leaves it on trial, judged below its
113
+ // scale. With one trial for the film the later step took its place, and the
114
+ // first stayed in force with no verdict: two Shots of a film late for its
115
+ // main thread, each stepping in turn, settled at 0.7 with a floor in each,
116
+ // though no step had paid. A scale above a step's ends its trial, the step
117
+ // no longer in force. The step paid when that window met its deadlines,
118
+ // whatever its GPU time: a GPU whose load fell lowers its clocks, so its
119
+ // time no longer falls with its work. Still late, it paid when its frames
120
+ // came faster (`typicalGap`) and its GPU p75 fell, each by `RELIEF_SHARE`:
121
+ // both, because frames that came faster while the GPU's time held came
122
+ // faster for something else, and GPU time that fell while the frames came
123
+ // no faster was not what held them (a film bound by its main thread: on GPU
124
+ // time alone, the integrated GPU kept cero at 0.85 though it played 24 to 26
125
+ // fps of 60 at every scale). Otherwise the step bought nothing, as it does
126
+ // for a film whose GPU time is draw calls and fixed passes, not pixels,
127
+ // which draws as long at 0.5 as at 1 and pays the copy on top. Such a step
128
+ // is undone at the next cut, back to the scale it was taken from with every
129
+ // step taken after it, whatever the windows before that cut say, and no
130
+ // window of that Shot steps below the scale the step was taken from until
131
+ // conditions change: one of its windows at that
132
+ // scale over `RETRY_LOAD` times the GPU load that prompted the step, or
133
+ // another Project, canvas size, mode or timer. Another Shot is not held back
134
+ // by it. Before this rule such a film stepped down window after window, each
135
+ // step predicted to fit, to the lowest step, and stayed there, softer and
136
+ // slower than at full density.
137
+ // - A transition window keeps the scale its first frame got
138
+ // (`PresentationSchedule`), and its frames are no samples and no deadlines:
139
+ // a dissolve draws both Shots, and a step it decided could land only at its
140
+ // own closing cut, softening the Shot after it to pay for frames already
141
+ // gone.
142
+ // - Pause draws at 1, because the child asks nothing while paused, and a
143
+ // stopped controller answers 1 whoever asks. What it settled on is kept for
144
+ // the next Play of the same Project at the same canvas size (`resting`, for
145
+ // which the stage holds its scaled targets while paused): another Project,
146
+ // or a canvas of another size, starts again from the top.
147
+ // - With no timer, Auto stays at 1: nothing measured, nothing lowered.
148
+ //
149
+ // `full` pins 1. `fast` is the steps from 0.7 down: it starts there, steps to
150
+ // 0.5 when the GPU needs it, and never goes above 0.7.
151
+ //
152
+ // ── THE THRESHOLDS ─────────────────────────────────────────────────────────
153
+ //
154
+ // Gate G1 (2026-10-24: an RTX 3060 Laptop on WebGPU, six films at device scale
155
+ // 1.5 and 2) measured GPU p95 at 0.5 to 3.7 ms against frame periods of 16.7
156
+ // to 41.7 ms, at most 0.15 of a period. That is far under `DOWN_SHARE`, so
157
+ // Auto never leaves 1 on such a GPU and full density alone is safe there. On
158
+ // the Linux test box's integrated GPU (AMD RDNA 2 through Vulkan, headless,
159
+ // device scale 2), tabs-a-launch-film played 19.2 fps of 60 at full density
160
+ // with a GPU p50 of 22 ms, and 26.9 to 27.8 at Auto, stepped to 0.5, with
161
+ // 11 ms. `LATE_SHARE` and `RELIEF_SHARE` are this rule's own, and no run has
162
+ // tuned them yet. With skipped frames counted, a fifth is the same bar at every
163
+ // rate: a stage that shows under five sixths of a film's frames, each of its
164
+ // frames skipping at most one. A 24 fps stage held to 20 fps, every frame
165
+ // three refreshes, skips one frame in five, the bar exactly, so whether its
166
+ // window reads late turns on where the window's edges fall.
167
+ import { DEFAULT_SHOT_FPS } from "@dailies-studio/shot/doc";
168
+ import type { PresentationFrame, PresentationScaleSource } from "@dailies-studio/shot";
169
+ import type { PlaybackResolution } from "../bridgeTypes.js";
170
+ import { percentile } from "./stagePerformanceTelemetry.js";
171
+
172
+ /** The scales a played frame may render at, as shares of the display's
173
+ * density, highest first. */
174
+ export const RENDER_SCALE_STEPS: readonly number[] = [1, 0.85, 0.7, 0.5];
175
+ /** `fast`: the steps from this one down. */
176
+ export const FAST_RENDER_SCALE = 0.7;
177
+ /** A window whose frames miss their deadline steps down while its GPU p75 is
178
+ * over this share of the frame period. */
179
+ export const DOWN_SHARE = 0.8;
180
+ /** One over the whole period is severe: it lands at the next frame. */
181
+ export const SEVERE_SHARE = 1;
182
+ /** A window is clean when the step above is predicted under this share. */
183
+ export const UP_SHARE = 0.7;
184
+ /** Clean windows in a row before a step up. */
185
+ export const UP_WINDOWS = 4;
186
+ /** A window misses its deadlines when this share of its presented frames, or
187
+ * more, came late (`deadlineMs`). */
188
+ export const LATE_SHARE = 0.2;
189
+ /** A window closes after this long once it holds `MIN_WINDOW_SAMPLES`: the
190
+ * second the stage's telemetry windows span (`ADAPTIVE_WINDOW_MS`,
191
+ * adaptivePresentation.ts), restated so this pure class imports no React. */
192
+ export const WINDOW_MS = 1_000;
193
+ /** The fewest samples a window closed by its second holds, and the fewest
194
+ * presented frames that say anything of its deadlines. */
195
+ export const MIN_WINDOW_SAMPLES = 8;
196
+ /** A step down whose Shot's next window is still late paid when that window's
197
+ * typical gap and its GPU p75 each fell by at least this share. */
198
+ export const RELIEF_SHARE = 0.1;
199
+ /** A step that did not pay is tried again once the load at the scale it was
200
+ * taken from is this many times the one that prompted it. */
201
+ export const RETRY_LOAD = 1.25;
202
+ /** The frames a timer sample lands after its draw, at most (gpuTimer.ts): the
203
+ * samples dropped after a Shot change, the old Shot's. */
204
+ export const SAMPLE_LAG_FRAMES = 3;
205
+ /** The most gaps a window keeps, its latest: ten seconds at 60 fps. Only a
206
+ * window no GPU sample closes for that long (no timer, or one that resolves
207
+ * nothing) gets that far. */
208
+ const GAPS_KEPT = 600;
209
+
210
+ /** A 60 Hz display's refresh, the one the playback benchmark judges its floor
211
+ * by (scripts/performance/floor.ts). */
212
+ const REFRESH_MS = 1_000 / 60;
213
+
214
+ // ponytail: a 60 Hz display assumed, so a faster one reads a frame one of its
215
+ // own refreshes late as on time; measure the refresh if that must count.
216
+ /** A gap past this went by a refresh without a frame. A frame of such a gap
217
+ * that skipped a document frame is late, whatever the cadence: the transport
218
+ * runs on wall time. Within one refresh a frame may skip by design, at a
219
+ * shuttle speed or a rate over the display's, which the stage is never told. */
220
+ export const MISSED_REFRESH_MS = 1.5 * REFRESH_MS;
221
+
222
+ /**
223
+ * The longest a presented frame may follow the one before and still have met
224
+ * its deadline: the most refreshes a 60 Hz display holds a frame of this
225
+ * cadence for, plus half a refresh. 24 fps alternates 33.3 and 50 ms there, so
226
+ * 58.3 ms; 30 and 60 fps hold every frame alike, so 41.7 and 25 ms. A rational
227
+ * rate a hair over a whole number of refreshes (29.97, 59.94) is held to that
228
+ * number, and its rare extra refresh reads as late. A stage that holds every
229
+ * frame that long and skips the ones it cannot show is caught by the frames
230
+ * it skips (`MISSED_REFRESH_MS`).
231
+ */
232
+ export function deadlineMs(periodMs: number): number {
233
+ return (Math.ceil(periodMs / REFRESH_MS - 0.05) + 0.5) * REFRESH_MS;
234
+ }
235
+
236
+ /** The gap a window's frames typically followed one another by: the mean of
237
+ * those at or under their p75. A stall is past it, and a mean is not held to
238
+ * the display's refreshes as a median is, so a film whose gaps straddle two
239
+ * refreshes does not read a refresh faster or slower from window to window. */
240
+ export function typicalGap(gaps: readonly number[]): number {
241
+ const p75 = percentile(gaps, 0.75)!;
242
+ const kept = gaps.filter((gap) => gap <= p75);
243
+ return kept.reduce((sum, gap) => sum + gap, 0) / kept.length;
244
+ }
245
+
246
+ /** `?renderScale=` on the stage URL, forwarded by the chrome's `stageSrc`:
247
+ * the heavy proofs' arm. `off` is no scale source at all, the stage before
248
+ * the playback render scale existed; a step answers that step on every
249
+ * played frame, whatever the GPU or the Preferences row says. Anything else,
250
+ * absent included, is null: the controller decides. */
251
+ export function stageRenderScaleArm(value: string | null): "off" | number | null {
252
+ if (value === "off") return "off";
253
+ return RENDER_SCALE_STEPS.find((step) => String(step) === value) ?? null;
254
+ }
255
+
256
+ /** The arm, read once, like `?rasterDensity` (adaptivePresentation.ts): the
257
+ * stage URL is set at iframe mount and never rewritten. */
258
+ export const STAGE_RENDER_SCALE: "off" | number | null =
259
+ typeof window === "undefined" ? null : stageRenderScaleArm(new URLSearchParams(window.location.search).get("renderScale"));
260
+
261
+ const ARM_SOURCE: PresentationScaleSource | null =
262
+ typeof STAGE_RENDER_SCALE === "number" ? fixedScale(STAGE_RENDER_SCALE) : null;
263
+
264
+ function fixedScale(scale: number): PresentationScaleSource {
265
+ return { scale: () => scale, resting: () => scale };
266
+ }
267
+
268
+ /** The source the stage hands the runtime and the split view: the
269
+ * controller, or the arm's (`STAGE_RENDER_SCALE`), null for `off`. The same
270
+ * object every call. */
271
+ export function stageScaleSource(controller: RenderScaleController): PresentationScaleSource | null {
272
+ if (STAGE_RENDER_SCALE === null) return controller;
273
+ return STAGE_RENDER_SCALE === "off" ? null : ARM_SOURCE;
274
+ }
275
+
276
+ /** The controller as the HUD prints it. */
277
+ export interface RenderScaleReading {
278
+ mode: PlaybackResolution;
279
+ /** What a played frame renders at now (`inForce`). */
280
+ scale: number;
281
+ /** A step decided and waiting for its frame, or null. */
282
+ next: number | null;
283
+ /** The budget: the document's frame period, ms. */
284
+ periodMs: number;
285
+ /** The last judged window's GPU p75, ms, at the scale it drew at. */
286
+ gpuP75Ms: number | null;
287
+ /** The last judged window's frames that missed their deadline, of those it
288
+ * presented, or null when it presented too few to say. */
289
+ late: { frames: number; of: number } | null;
290
+ /** The renderer has a GPU timer. */
291
+ timer: boolean;
292
+ /** Why the scale last moved, or null before it did. */
293
+ reason: string | null;
294
+ }
295
+
296
+ /** One frame's GPU time, and the shares of it its passes up to the canvas
297
+ * took: the copy of P, and the present (the header's prediction). */
298
+ interface Sample {
299
+ gpuMs: number;
300
+ copyMs: number;
301
+ presentMs: number;
302
+ }
303
+
304
+ /** A window's deadlines: how many of its presented frames came late, of how
305
+ * many, and the gap they typically followed one another by. */
306
+ interface Deadlines {
307
+ late: number;
308
+ presented: number;
309
+ typicalMs: number;
310
+ }
311
+
312
+ /** How long a presented frame followed the one before, and how many document
313
+ * frames it moved on. */
314
+ interface Gap {
315
+ ms: number;
316
+ frames: number;
317
+ }
318
+
319
+ /** A Shot's passes up to the canvas, p75 ms, from its last window below 1:
320
+ * what a frame of it adds at every scale (the present, which a frame at 1
321
+ * draws untagged as three's output pass, and a Shot with post never draws)
322
+ * and below 1 (the copy of P). */
323
+ interface CanvasPasses {
324
+ copyMs: number;
325
+ presentMs: number;
326
+ }
327
+
328
+ const NO_CANVAS_PASSES: CanvasPasses = { copyMs: 0, presentMs: 0 };
329
+
330
+ /** A step down and what decided it: the p75 it was taken at, what the
331
+ * prediction said it would cost, the Shot of the window that did, and that
332
+ * window's typical gap. */
333
+ interface Step {
334
+ from: number;
335
+ to: number;
336
+ p75: number;
337
+ predicted: number;
338
+ shot: string | null;
339
+ typicalMs: number;
340
+ }
341
+
342
+ /** In one Shot, a step below `scale` did not pay at `load` there. */
343
+ interface Floor {
344
+ scale: number;
345
+ load: number;
346
+ }
347
+
348
+ const ms = (value: number): string => `${value.toFixed(1)}ms`;
349
+ const lateText = (deadlines: Deadlines): string => `${deadlines.late} of ${deadlines.presented} frames late`;
350
+
351
+ export class RenderScaleController implements PresentationScaleSource {
352
+ #mode: PlaybackResolution = "auto";
353
+ #timer = false;
354
+ #playing = false;
355
+ #periodMs = 0;
356
+ #windowSize = 1;
357
+ /** The canvas's backing pixels the kept scale was settled for. */
358
+ #pixels = 0;
359
+ /** The scale the frames render at: applied only as a frame asks, or by a
360
+ * mode or timer change. Kept across Pause, so the next Play starts here. */
361
+ #scale = 1;
362
+ /** A frame asked this run, so `#scale` is in force. Nothing asks in a
363
+ * layout the child does not draw, and no window is judged there. */
364
+ #asked = false;
365
+ /** The frame asking last was inside a transition window. */
366
+ #blending = false;
367
+ /** The controller's own step, waiting for a cut, or for the next frame
368
+ * when `#now`. */
369
+ #pending: number | null = null;
370
+ #now = false;
371
+ /** `#pending` undoes a step that did not pay: no window moves it before
372
+ * the cut it waits for. */
373
+ #undoing = false;
374
+ #samples: Sample[] = [];
375
+ /** The gaps between the frames presented while the window in progress
376
+ * gathered its samples. */
377
+ #gaps: Gap[] = [];
378
+ /** The last frame presented that a gap may start at: when its request was
379
+ * timed and its document frame. Null at the start of a run, at a cut's
380
+ * frame and inside a transition window (the header's "A WINDOW IS ONE
381
+ * SHOT'S"). */
382
+ #shown: { atMs: number; frame: number } | null = null;
383
+ /** The frame asking last is a cut's, until it is presented. */
384
+ #cut = false;
385
+ /** The Shot the window in progress draws: undefined until a frame asks. */
386
+ #windowShot: string | null | undefined = undefined;
387
+ /** Samples still to drop after a Shot change. */
388
+ #skip = 0;
389
+ /** When the window in progress took its first sample. */
390
+ #windowStart = 0;
391
+ /** The next whole window is not judged: the one after a step. */
392
+ #settle = false;
393
+ #clean = 0;
394
+ #lastP75: number | null = null;
395
+ #lastDeadlines: Deadlines | null = null;
396
+ /** By Shot, its passes up to the canvas, from its last window judged below
397
+ * 1 (the header's prediction). */
398
+ #canvas = new Map<string | null, CanvasPasses>();
399
+ /** The last window's below 1, any Shot's: what a Shot that has not drawn
400
+ * below 1 is priced with. */
401
+ #lastCanvas: CanvasPasses = NO_CANVAS_PASSES;
402
+ #reason: string | null = null;
403
+ /** The step down waiting in `#pending`, with what decided it. */
404
+ #decided: Step | null = null;
405
+ /** By Shot, its step down in force, until the first window of that Shot at
406
+ * the step's scale or below says whether it paid. */
407
+ #trials = new Map<string, Step>();
408
+ /** By Shot: a step below `scale` did not pay at `load` there, so no window
409
+ * of the Shot tries one while they stay under `RETRY_LOAD` times it. */
410
+ #floors = new Map<string, Floor>();
411
+
412
+ constructor() {
413
+ this.setFrameRate(DEFAULT_SHOT_FPS);
414
+ }
415
+
416
+ /** The Preferences row (bridge v52 `EditorChrome.playbackResolution`). */
417
+ setMode(mode: PlaybackResolution): void {
418
+ if (mode === this.#mode) return;
419
+ this.#mode = mode;
420
+ this.#retarget(mode);
421
+ }
422
+
423
+ /** Whether the renderer can time a pass (`GpuTimer`'s answer). */
424
+ setTimer(available: boolean): void {
425
+ if (available === this.#timer) return;
426
+ this.#timer = available;
427
+ this.#retarget(available ? "gpu timer" : "no gpu timer");
428
+ }
429
+
430
+ /** The document's rate: the budget, and how many frames make a window. */
431
+ setFrameRate(fps: { num: number; den: number }): void {
432
+ if (!(fps.num > 0 && fps.den > 0 && Number.isFinite(fps.num / fps.den))) return;
433
+ this.#periodMs = (1000 * fps.den) / fps.num;
434
+ this.#windowSize = Math.max(1, Math.round(fps.num / fps.den));
435
+ }
436
+
437
+ /** The canvas's backing pixels (CSS size times density). A scale settled
438
+ * for another size says nothing about this one: a step that fit a smaller
439
+ * canvas misses on a larger one, and one a larger canvas needed is too soft
440
+ * for a smaller, so it starts again from the top. */
441
+ setCanvasPixels(pixels: number): void {
442
+ if (!(pixels > 0) || pixels === this.#pixels) return;
443
+ const first = this.#pixels === 0;
444
+ this.#pixels = pixels;
445
+ if (!first) this.reset();
446
+ }
447
+
448
+ /** The transport runs. Only its frames are samples and deadlines: a
449
+ * preparing pass's and a paused stage's draw at full density and decide
450
+ * nothing. */
451
+ setPlaying(playing: boolean): void {
452
+ if (playing === this.#playing) return;
453
+ this.#playing = playing;
454
+ this.#samples = [];
455
+ this.#gaps = [];
456
+ this.#shown = null;
457
+ this.#cut = false;
458
+ this.#windowShot = undefined;
459
+ this.#skip = 0;
460
+ this.#asked = false;
461
+ this.#blending = false;
462
+ }
463
+
464
+ /** Another Project: what this one settled on says nothing about it. */
465
+ reset(): void {
466
+ this.#scale = this.#steps()[0]!;
467
+ this.#pending = null;
468
+ this.#now = false;
469
+ this.#undoing = false;
470
+ this.#samples = [];
471
+ this.#gaps = [];
472
+ this.#shown = null;
473
+ this.#cut = false;
474
+ this.#settle = false;
475
+ this.#clean = 0;
476
+ this.#lastP75 = null;
477
+ this.#lastDeadlines = null;
478
+ this.#canvas.clear();
479
+ this.#lastCanvas = NO_CANVAS_PASSES;
480
+ this.#reason = null;
481
+ this.#decided = null;
482
+ this.#trials.clear();
483
+ this.#floors.clear();
484
+ }
485
+
486
+ /** The scale the next Play's first frame gets (`PresentationScaleSource
487
+ * .resting`): the one kept across Pause, or a step waiting for that frame,
488
+ * which is a cut. The stage's idle warm-up holds its scaled targets for it. */
489
+ resting(): number {
490
+ return this.#pending ?? this.#scale;
491
+ }
492
+
493
+ /** One frame's GPU time, ms, as the timer resolved it at `atMs`, and how
494
+ * much of it was the copy of P and the present (gpuTimer.ts). A frame
495
+ * inside a transition window is no sample. */
496
+ observe(gpuMs: number, atMs: number, copyMs = 0, presentMs = 0): void {
497
+ if (!this.#playing || this.#blending || !(gpuMs >= 0 && Number.isFinite(gpuMs))) return;
498
+ if (this.#skip > 0) {
499
+ this.#skip -= 1;
500
+ return;
501
+ }
502
+ if (this.#samples.length === 0) this.#windowStart = atMs;
503
+ const copy = copyMs > 0 && Number.isFinite(copyMs) ? Math.min(copyMs, gpuMs) : 0;
504
+ const present = presentMs > 0 && Number.isFinite(presentMs) ? Math.min(presentMs, gpuMs - copy) : 0;
505
+ this.#samples.push({ gpuMs, copyMs: copy, presentMs: present });
506
+ const full = this.#samples.length >= this.#windowSize;
507
+ const timed = this.#samples.length >= MIN_WINDOW_SAMPLES && atMs - this.#windowStart >= WINDOW_MS;
508
+ if (full || timed) this.#closeWindow();
509
+ }
510
+
511
+ /** Document frame `frame` of the transport reached the canvas, its request
512
+ * timed at `atMs` before it was drawn (bridgeClient.ts, the telemetry's
513
+ * match of the runtime's echo): the deadline half of a window. No gap
514
+ * starts at a cut's frame or a transition window's, nor ends at one (the
515
+ * header's "A WINDOW IS ONE SHOT'S"). */
516
+ presented(atMs: number, frame: number): void {
517
+ if (!this.#playing || !Number.isFinite(atMs) || !Number.isFinite(frame)) return;
518
+ const last = this.#shown;
519
+ this.#shown = this.#blending || this.#cut ? null : { atMs, frame };
520
+ this.#cut = false;
521
+ if (last === null || this.#blending || atMs < last.atMs) return;
522
+ if (this.#gaps.length >= GAPS_KEPT) this.#gaps.shift();
523
+ this.#gaps.push({ ms: atMs - last.atMs, frames: Math.abs(frame - last.frame) });
524
+ }
525
+
526
+ /** The window in progress is over: judged, unless it is the one after a
527
+ * step. */
528
+ #closeWindow(): void {
529
+ const window = this.#samples;
530
+ const gaps = this.#gaps;
531
+ this.#samples = [];
532
+ this.#gaps = [];
533
+ if (this.#settle) {
534
+ this.#settle = false;
535
+ return;
536
+ }
537
+ if (this.#asked) this.#judge(window, this.#deadlines(gaps), this.#windowShot ?? null);
538
+ }
539
+
540
+ /** What a window's presented frames say of its deadlines, or null when
541
+ * there were too few of them to say anything. */
542
+ #deadlines(gaps: readonly Gap[]): Deadlines | null {
543
+ if (gaps.length < MIN_WINDOW_SAMPLES) return null;
544
+ const allowed = deadlineMs(this.#periodMs);
545
+ const late = gaps.filter((gap) => gap.ms > allowed || (gap.frames > 1 && gap.ms > MISSED_REFRESH_MS)).length;
546
+ return { late, presented: gaps.length, typicalMs: typicalGap(gaps.map((gap) => gap.ms)) };
547
+ }
548
+
549
+ /** The child's question, once per played frame (`PresentationScaleSource`).
550
+ * Only a transition window's first frame asks, and the window keeps that
551
+ * answer; its frames are no samples until the next ask, its closing cut. A
552
+ * stopped controller answers 1: a preparing pass draws at full density
553
+ * whoever asks. */
554
+ scale(frame: PresentationFrame): number {
555
+ if (!this.#playing) return 1;
556
+ this.#asked = true;
557
+ this.#blending = frame.blending;
558
+ // The header's "A WINDOW IS ONE SHOT'S": no gap ends at a cut's frame,
559
+ // and none starts there once it is presented.
560
+ this.#cut = frame.cut;
561
+ if (frame.cut) this.#shown = null;
562
+ if (frame.shot !== this.#windowShot) {
563
+ if (this.#windowShot !== undefined) {
564
+ if (this.#samples.length >= MIN_WINDOW_SAMPLES) this.#closeWindow();
565
+ else {
566
+ this.#samples = [];
567
+ this.#gaps = [];
568
+ }
569
+ this.#skip = SAMPLE_LAG_FRAMES;
570
+ }
571
+ this.#windowShot = frame.shot;
572
+ this.#shown = null;
573
+ }
574
+ if (this.#pending !== null && (frame.cut || this.#now)) this.#apply(this.#pending, this.#reason);
575
+ return this.#scale;
576
+ }
577
+
578
+ /** What a played frame renders at now: 1 while stopped, and before a frame
579
+ * asked this run. */
580
+ inForce(): number {
581
+ return this.#playing && this.#asked ? this.#scale : 1;
582
+ }
583
+
584
+ reading(): RenderScaleReading {
585
+ const deadlines = this.#lastDeadlines;
586
+ return {
587
+ mode: this.#mode,
588
+ scale: this.inForce(),
589
+ next: this.#pending,
590
+ periodMs: this.#periodMs,
591
+ gpuP75Ms: this.#lastP75,
592
+ late: deadlines === null ? null : { frames: deadlines.late, of: deadlines.presented },
593
+ timer: this.#timer,
594
+ reason: this.#reason,
595
+ };
596
+ }
597
+
598
+ /** The steps this mode may take, highest first. Without a timer nothing is
599
+ * measured, so nothing goes below where the mode starts. */
600
+ #steps(): readonly number[] {
601
+ if (this.#mode === "full") return [1];
602
+ const steps = this.#mode === "fast" ? RENDER_SCALE_STEPS.filter((step) => step <= FAST_RENDER_SCALE) : RENDER_SCALE_STEPS;
603
+ return this.#timer ? steps : steps.slice(0, 1);
604
+ }
605
+
606
+ /** The mode or the timer moved the steps: the highest one at or below the
607
+ * scale, else the lowest, at once. A setting the author changed shows. */
608
+ #retarget(reason: string): void {
609
+ const steps = this.#steps();
610
+ this.#pending = null;
611
+ this.#now = false;
612
+ this.#undoing = false;
613
+ this.#clean = 0;
614
+ // Other steps: what the old ones learned says nothing about them.
615
+ this.#decided = null;
616
+ this.#trials.clear();
617
+ this.#floors.clear();
618
+ const target = steps.find((step) => step <= this.#scale) ?? steps.at(-1)!;
619
+ if (target !== this.#scale) this.#apply(target, reason);
620
+ }
621
+
622
+ #apply(scale: number, reason: string | null): void {
623
+ const decided = this.#decided;
624
+ this.#pending = null;
625
+ this.#now = false;
626
+ this.#undoing = false;
627
+ this.#decided = null;
628
+ if (scale === this.#scale) return;
629
+ // A step down the windows of a Shot chose is on trial until a window of
630
+ // that Shot judges it, whatever steps follow it, and a scale above a
631
+ // step's ends its trial (the header's "A STEP DOWN MUST PAY").
632
+ for (const [shot, trial] of this.#trials) if (trial.to < scale) this.#trials.delete(shot);
633
+ if (decided !== null && decided.shot !== null && decided.from === this.#scale && decided.to === scale) this.#trials.set(decided.shot, decided);
634
+ this.#scale = scale;
635
+ this.#reason = reason;
636
+ this.#clean = 0;
637
+ this.#samples = [];
638
+ this.#gaps = [];
639
+ this.#settle = true;
640
+ }
641
+
642
+ #judge(window: readonly Sample[], deadlines: Deadlines | null, shot: string | null): void {
643
+ const p75 = percentile(window.map((sample) => sample.gpuMs), 0.75)!;
644
+ this.#lastP75 = p75;
645
+ this.#lastDeadlines = deadlines;
646
+ const steps = this.#steps();
647
+ const scale = this.#scale;
648
+ const down = DOWN_SHARE * this.#periodMs;
649
+ // The header's "A REAL PROBLEM, AND THE GPU'S": its frames late.
650
+ const late = deadlines !== null && deadlines.late >= LATE_SHARE * deadlines.presented;
651
+ // This Shot's step down in force, judged by the first window of the Shot
652
+ // at its scale or below that says anything of deadlines (the header's "A
653
+ // STEP DOWN MUST PAY"). Another Shot's window leaves it waiting.
654
+ const trial = shot === null ? undefined : this.#trials.get(shot);
655
+ if (shot !== null && trial !== undefined && deadlines !== null) {
656
+ this.#trials.delete(shot);
657
+ if (late && !(deadlines.typicalMs <= (1 - RELIEF_SHARE) * trial.typicalMs && p75 <= (1 - RELIEF_SHARE) * trial.p75)) {
658
+ this.#floors.set(shot, { scale: trial.from, load: trial.p75 });
659
+ this.#clean = 0;
660
+ this.#pending = trial.from;
661
+ this.#undoing = true;
662
+ this.#decided = null;
663
+ this.#now = false;
664
+ this.#reason = `no relief at ${scale}: ${lateText(deadlines)}, gpu p75 ${ms(p75)} against ${ms(trial.p75)} at ${trial.from}`;
665
+ return;
666
+ }
667
+ }
668
+ // The undo of a step that bought nothing waits for its cut, whatever the
669
+ // windows before it say: over the budget, the floor leaves them no step
670
+ // to take, and under it, no step up to keep.
671
+ if (this.#undoing) return;
672
+ // A load past the one a step did not pay at, in that Shot, is new
673
+ // conditions there.
674
+ let floor = shot === null ? undefined : this.#floors.get(shot);
675
+ if (shot !== null && floor !== undefined && scale === floor.scale && p75 > RETRY_LOAD * floor.load) {
676
+ this.#floors.delete(shot);
677
+ floor = undefined;
678
+ }
679
+ if (scale < 1) {
680
+ this.#lastCanvas = {
681
+ copyMs: percentile(window.map((sample) => sample.copyMs), 0.75)!,
682
+ presentMs: percentile(window.map((sample) => sample.presentMs), 0.75)!,
683
+ };
684
+ this.#canvas.set(shot, this.#lastCanvas);
685
+ }
686
+ // What the window's own passes would have cost at the display's density:
687
+ // a step's prediction is this times its area, the present on top of it,
688
+ // and the copy below 1 (the header's prediction). At 1 the present is in
689
+ // the frames' time untagged, three's own output pass.
690
+ const passes = this.#canvas.get(shot) ?? this.#lastCanvas;
691
+ const own = percentile(window.map((sample) => sample.gpuMs - sample.copyMs - sample.presentMs), 0.75)!;
692
+ const perPixel = Math.max(0, own - (scale === 1 ? passes.presentMs : 0)) / (scale * scale);
693
+ const cost = (step: number): number => perPixel * step * step + passes.presentMs + (step < 1 ? passes.copyMs : 0);
694
+ if (late && p75 > down) {
695
+ this.#clean = 0;
696
+ // Not below where a step did not pay in this Shot.
697
+ const lowest = floor?.scale ?? 0;
698
+ const below = steps.filter((step) => step < scale && step >= lowest);
699
+ const target = below.find((step) => cost(step) <= down) ?? below.at(-1);
700
+ if (target === undefined) {
701
+ this.#pending = null;
702
+ this.#decided = null;
703
+ return;
704
+ }
705
+ this.#pending = target;
706
+ this.#decided = { from: scale, to: target, p75, predicted: cost(target), shot, typicalMs: deadlines.typicalMs };
707
+ this.#now = p75 > SEVERE_SHARE * this.#periodMs;
708
+ this.#reason = `${lateText(deadlines)}, gpu p75 ${ms(p75)} over ${ms(down)}${this.#now ? ", severe" : ""}`;
709
+ return;
710
+ }
711
+ if (this.#pending !== null && this.#pending < scale) {
712
+ this.#pending = null;
713
+ this.#decided = null;
714
+ }
715
+ // The step just above: the last of those higher, highest first.
716
+ const up = steps.filter((step) => step > scale).at(-1);
717
+ const headroom = UP_SHARE * this.#periodMs;
718
+ if (up !== undefined && cost(up) <= headroom) {
719
+ this.#clean += 1;
720
+ if (this.#clean < UP_WINDOWS) return;
721
+ this.#pending = up;
722
+ this.#decided = null;
723
+ this.#now = false;
724
+ this.#reason = `${UP_WINDOWS} windows with ${ms(cost(up))} predicted under ${ms(headroom)}`;
725
+ return;
726
+ }
727
+ this.#clean = 0;
728
+ if (this.#pending !== null && this.#pending > scale) this.#pending = null;
729
+ }
730
+ }