Skip to content

feat(core): add render-function adapter for time-driven compositions#1640

Open
iret77 wants to merge 1 commit into
heygen-com:mainfrom
iret77:feat/render-fn-adapter
Open

feat(core): add render-function adapter for time-driven compositions#1640
iret77 wants to merge 1 commit into
heygen-com:mainfrom
iret77:feat/render-fn-adapter

Conversation

@iret77

@iret77 iret77 commented Jun 22, 2026

Copy link
Copy Markdown

Summary

Adds a render-function adapter (render-fn) so compositions whose visual
state is a pure function of time can render deterministically without an
animation library.

Today's adapters cover GSAP, WAAPI/CSS, anime.js, Lottie, and the GPU/map
libraries. A composition that draws each frame from a single clock value — a
hand-rolled clock, a React (or other framework) state timeline, or a
<canvas> / demoscene loop — registers no timeline and uses none of those
libraries, so __hf.seek(t) has nothing to drive and capture yields blank or
frozen frames. The requestAnimationFrame loop such compositions use for live
playback does not advance under deterministic capture.

This adapter is the lowest-common-denominator path: the composition registers a
render(timeSeconds) callback and the runtime calls it with the exact
composition time for every captured frame — the seek-driven replacement for the
requestAnimationFrame loop.

Contract

window.__hfRender = window.__hfRender || [];
window.__hfRender.push((timeSeconds) => drawFrame(timeSeconds));
  • Callbacks are invoked with the clamped seek time in seconds, in registration
    order, for every captured frame.
  • The current time is mirrored onto window.__hfTime for draw helpers that
    prefer to read it instead of performance.now().
  • Same determinism rules as every other adapter: render purely from the time
    argument — no Date.now(), no performance.now(), no unseeded randomness.
  • Pure no-op when no callback is registered, so existing compositions are
    unaffected.

It mirrors the existing registry-array pattern (__hfLottie, __hfAnime,
__hfMapbox, …): discover reads lazily, seek drives all callbacks with
per-callback error isolation, pause is a no-op (a render function has no
running clock to stop). The callback list is snapshotted before iteration so a
callback registering another callback can't be invoked mid-seek or loop.

Changes

  • packages/core/src/runtime/adapters/render-fn.ts — the adapter
  • packages/core/src/runtime/adapters/render-fn.test.ts — unit tests
    (repeatability, arbitrary seek order, bounds/clamping, error isolation,
    snapshot safety, no-op when empty)
  • packages/core/src/runtime/init.ts — register the adapter
  • packages/core/src/runtime/window.d.ts__hfRender / __hfTime types
  • docs/concepts/frame-adapters.mdx — Supported Runtimes row + section
  • skills/hyperframes-animation/ — routing entry + adapters/render-fn.md

Test plan

bun run --filter @hyperframes/core test
bun run --filter '*' typecheck
bun run lint
bun run format:check

Compositions whose visual state is a pure function of time — a hand-rolled
clock, a framework state timeline, or a canvas/demoscene loop — register no
GSAP timeline and use none of the existing library adapters, so __hf.seek(t)
has nothing to drive and capture yields blank frames. The requestAnimationFrame
loop they use for live playback does not advance under deterministic capture.

Add a render-function adapter: the composition pushes render(timeSeconds)
callbacks onto window.__hfRender and the runtime invokes them with the exact
composition time for every captured frame. Mirrors the current time onto
window.__hfTime for poll-style draw helpers.

Follows the existing registry-array pattern (__hfLottie/__hfAnime/__hfMapbox):
lazy discover, per-callback error isolation on seek, no-op pause, and a
snapshot of the callback list before iteration. Pure no-op when no callback is
registered, so existing compositions are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant