@@ -109,6 +109,7 @@ describe("initSandboxRuntimeModular", () => {
109109 delete window . __player ;
110110 delete window . __playerReady ;
111111 delete window . __renderReady ;
112+ delete ( window as { __HF_EXPORT_RENDER_SEEK_CONFIG ?: unknown } ) . __HF_EXPORT_RENDER_SEEK_CONFIG ;
112113 delete window . __hfTimelinesBuilding ;
113114 delete ( window as { THREE ?: unknown } ) . THREE ;
114115 vi . restoreAllMocks ( ) ;
@@ -146,6 +147,99 @@ describe("initSandboxRuntimeModular", () => {
146147 expect ( child . style . visibility ) . toBe ( "visible" ) ;
147148 } ) ;
148149
150+ it ( "uses export render fps when quantizing renderSeek" , ( ) => {
151+ const infoSpy = vi . spyOn ( console , "info" ) . mockImplementation ( ( ) => { } ) ;
152+ const root = document . createElement ( "div" ) ;
153+ root . setAttribute ( "data-composition-id" , "main" ) ;
154+ root . setAttribute ( "data-root" , "true" ) ;
155+ root . setAttribute ( "data-start" , "0" ) ;
156+ root . setAttribute ( "data-duration" , "1" ) ;
157+ root . setAttribute ( "data-width" , "1920" ) ;
158+ root . setAttribute ( "data-height" , "1080" ) ;
159+ document . body . appendChild ( root ) ;
160+
161+ const timeline = createMockTimeline ( 1 ) ;
162+ window . __timelines = { main : timeline } ;
163+ (
164+ window as {
165+ __HF_EXPORT_RENDER_SEEK_CONFIG ?: { fps : number ; fpsSource : "render-options" } ;
166+ }
167+ ) . __HF_EXPORT_RENDER_SEEK_CONFIG = {
168+ fps : 60 ,
169+ fpsSource : "render-options" ,
170+ } ;
171+
172+ initSandboxRuntimeModular ( ) ;
173+
174+ window . __player ?. renderSeek ( 1 / 60 ) ;
175+
176+ expect ( timeline . time ( ) ) . toBeCloseTo ( 1 / 60 , 6 ) ;
177+ expect ( infoSpy ) . toHaveBeenCalledWith (
178+ "[hyperframes] render runtime fps" ,
179+ expect . objectContaining ( {
180+ canonicalFps : 60 ,
181+ source : "render-options" ,
182+ rawFpsSource : "render-options" ,
183+ rawFps : 60 ,
184+ } ) ,
185+ ) ;
186+ } ) ;
187+
188+ it ( "surfaces unknown export render fps sources without collapsing them to render-options" , ( ) => {
189+ const infoSpy = vi . spyOn ( console , "info" ) . mockImplementation ( ( ) => { } ) ;
190+ const root = document . createElement ( "div" ) ;
191+ root . setAttribute ( "data-composition-id" , "main" ) ;
192+ root . setAttribute ( "data-root" , "true" ) ;
193+ root . setAttribute ( "data-start" , "0" ) ;
194+ root . setAttribute ( "data-duration" , "1" ) ;
195+ root . setAttribute ( "data-width" , "1920" ) ;
196+ root . setAttribute ( "data-height" , "1080" ) ;
197+ document . body . appendChild ( root ) ;
198+
199+ window . __timelines = { main : createMockTimeline ( 1 ) } ;
200+ (
201+ window as {
202+ __HF_EXPORT_RENDER_SEEK_CONFIG ?: { fps : number ; fpsSource : string } ;
203+ }
204+ ) . __HF_EXPORT_RENDER_SEEK_CONFIG = {
205+ fps : 60 ,
206+ fpsSource : "future-source" ,
207+ } ;
208+
209+ initSandboxRuntimeModular ( ) ;
210+
211+ expect ( infoSpy ) . toHaveBeenCalledWith (
212+ "[hyperframes] render runtime fps" ,
213+ expect . objectContaining ( {
214+ canonicalFps : 60 ,
215+ source : "unknown" ,
216+ rawFpsSource : "future-source" ,
217+ } ) ,
218+ ) ;
219+ } ) ;
220+
221+ it ( "keeps the default 30fps renderSeek grid when export render fps is absent" , ( ) => {
222+ const root = document . createElement ( "div" ) ;
223+ root . setAttribute ( "data-composition-id" , "main" ) ;
224+ root . setAttribute ( "data-root" , "true" ) ;
225+ root . setAttribute ( "data-start" , "0" ) ;
226+ root . setAttribute ( "data-duration" , "1" ) ;
227+ root . setAttribute ( "data-width" , "1920" ) ;
228+ root . setAttribute ( "data-height" , "1080" ) ;
229+ document . body . appendChild ( root ) ;
230+
231+ const timeline = createMockTimeline ( 1 ) ;
232+ window . __timelines = { main : timeline } ;
233+
234+ initSandboxRuntimeModular ( ) ;
235+
236+ // This is the originally broken 60fps render sample under the historical
237+ // 30fps runtime default: floor((1 / 60) * 30) / 30 = 0.
238+ window . __player ?. renderSeek ( 1 / 60 ) ;
239+
240+ expect ( timeline . time ( ) ) . toBe ( 0 ) ;
241+ } ) ;
242+
149243 it ( "uses live child timeline duration when a composition host has no authored duration" , ( ) => {
150244 const root = document . createElement ( "div" ) ;
151245 root . setAttribute ( "data-composition-id" , "main" ) ;
0 commit comments