Commit Graph
1039 Commits
Author SHA1 Message Date
Miguel Ángel 4ceec0171f perf(studio-server): preview steps skip the page parse when a tag scan answers (#7092)
* test(studio-server): a stamped preview build parses its page once

* perf(studio-server): preview steps skip the page parse when a tag scan answers
2026-11-03 14:16:22 +00:00
Miguel Ángel 47dddedd6c fix(core): Safari audio frozen after its cut is pulled onto the playhead at 0.5 s behind, not 1 s (#7082)
* fix(core): Safari audio that sits frozen after its cut is pulled onto the playhead in 0.4 s, not 1 s

* fix(core): shorten the frozen-audio comment

* fix(core): leave a WebKit audio element that is still filling data out of the frozen rule
2026-11-03 11:17:40 +00:00
WaterrrForeverandClaude Opus 5.5 2daedcdafc feat(skills): style templates on the HyperFrames plan (#6842)
* fix(cli,skills): say a step's credits are its total, and keep cost out of retries

An out-of-credits refusal named what the step needs, and an agent read
that as the shortfall. It now says the number is the step's total.

After its last try, an agent told the person what was spent and what
another try would cost. The HeyGen reference now says not to.

* fix(cli): plan avatar credits from the HyperFrames price rows

An avatar's credits came from its old API price, about five times what
the plan charges, so --max-credits refused calls that fit and a top-up
asked for too much. They now use the Avatar IV photo row plus the
general avatar video row, 950 credits a minute.

* fix(cli): pin the avatar price test and keep pricing under the comment share

* fix(cli): plan avatar seconds on the Avatar IV row alone

The general avatar row is only checked at admission and billed on
seconds left after Avatar IV, so adding it to every second made a 15 s
script plan at 238 instead of 200 and a 220 limit refuse a call that fits.

* feat(skills): creator templates on the HyperFrames plan

A Pro account asks for a video in a creator's format and the agent pulls that
template on demand. The packs sit in the private bucket behind the plan-gated
bundle route, so a Free account gets a 402 and the upgrade step. A pull costs
no credits; making the film spends them as Reverse does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): make creator-remix the one template skill

SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): the creator-remix skill and its packs

SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(skills): word creator-remix's sign-in steps the way media-use does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): a creator-remix presenter runs on the plan by default

The person's own HeyGen avatar is recommended, not required: without
one, 小Lin's presenter is made with HeyGen Video 1 from their photo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cli): pin template packs whose voice bills the plan first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): offload media named inside template tags

References in a composition's <template> were never rewritten, because the DOM
query stops at the tag and template.content does not serialize, so the file stayed
in the zip. A stem matched as a plain word and a same-named file elsewhere counted
as a mention too. A 5-minute presented film went from 196 MB to 71 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cli): lipsync, and avatars that speak a finished voice

Video 1 takes audio as a reference and re-paces it, so a shot's lips follow its
own track and drift up to a second from the voice. generate --type lipsync redoes
the mouth to the exact voice. generate --type avatar takes --audio, and --avatar
for the person's own look, so the lips land on the voice from one job.

Also moves the plan rows to the 2026-10-30 price book, estimates a voice's credits
so --max-credits can hold it, logs jobs from a folder with no project, and describes
the resolve flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): presenters go avatar first, and a voice can be stock

The person's own avatar, then Avatar IV from their photo, then Video 1 with
lipsync. With no clone or recording, a stock voice named in the credits. Pins the
five rebuilt packs. The archive test keeps its ../ under GNU tar with -P.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cli): fix avatar price and sfx provider tests, trim comments

A wav gives the avatar price test an exact length on every platform. The
sfx tests now expect HeyGen first. Comment share is back under the base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): read a font shorthand whose size nests parentheses

`font: calc(var(--size) * 1px)/1 "X"` cut at the first closing parenthesis, so a
bad family name failed the render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): one ledger row per job, and search skips a template's own pictures

A job done at once logs one row, transcribe logs to the project, and resolve no
longer offers a component's file for a search. --reuse and --provider get help text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): pin the tested template packs, and a board needs no voice

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cli): read the transcribe ledger row as JSON, for Windows paths

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cli): pin the Big Questions pack without the film length check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): call them style templates, and the docs example pulls a real slug

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cli): pin the cleaned template packs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): drop the stock-presenter ban and the no-spend-before-storyboard rule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cli): pin the Paper Trail and Long Story Short packs without the stock-presenter ban

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): rename creator-remix to style-remix, and the five templates to names that say the look

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): style-remix lists the five templates when none is named

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cli): pin the Finance Explainer pack that varies its motion graphics

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cli): media-use generate --type cutout takes a shot out of its background on the plan

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cli): pin the Finance Explainer pack without the provisional variety rule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cli): pin the five style packs with variety rules measured on real films

Animated Science gets a worlds-and-boards budget, 3D True Crime a main board and a
brief corner note, 3D Brand caps by measured rate, Finance and Collage set pieces and
more than one look.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): media-use names the on-plan cutout and lipsync commands, not the heygen CLI ones

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): cutout retries dedupe on request_id, uploads carry their real type; style-remix is public

The background-removal route dedupes on the body's request_id and ignores the Idempotency-Key
header, so a retry after a lost answer started a second job: runJob now also sends the key in the
body for such a route. Uploads typed every non-MP4 file as audio, so WebM and MOV inputs were
refused: they now take their type from the extension. Fails without the fix: the new cutout test
in generate.test.ts sees audio/mpeg for WebM and MOV.

style-remix drops metadata.internal and is listed with the other published skills.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cli): keep the comment share of the touched files at main's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: count style-remix in the README and CLAUDE.md skill lists

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(skills): rename style-remix to style-sampling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:07:02 +00:00
James RussoandClaude Opus 5.5 d89bf35b1d fix(core): keep overflowing content and SVG clip paths in motion-blurred frames (#7080)
* fix(core): keep overflowing content and SVG clip paths in motion-blurred frames

A blurred mover was copied only within its own layout box, so text wider
than the box (a one-line wordmark in a fixed-width container) was cut in
blurred frames. The movers-alone capture also hid every non-mover, including
the SVG clipPath a mover references, which empties the clip and makes the
mover vanish. Both showed as flicker during playback and in full renders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): keep the motion-blur fix within each file's comment share

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): measure overflowing text with a Range, ignore rects inside the mover

A block child is as wide as its mover however long its no-wrap text, so
reading child boxes missed the common overflow layout. Read the text's own
rects through a Range. On a rotated mover each child's upright client rect
covers the mover's corners, which doubled the box at 45 degrees and could
pull a neighbouring mover into the trail; only rects that leave the
mover's own client rect now grow it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): measure a rotated mover's overflow unrotated

The upright client rect of a rotated mover has empty corners, so overflow
that landed there passed the "stays inside" check and was still cropped.
Measure a rotated mover with its own transform off and map the rects back
through its layout pose, then restore its style.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): keep a rotated mover the containing block while measured

With only transform: none, a mover with no position stops being the
containing block of its absolute and fixed children, so they jump to an
outer ancestor while measured and their real overflow is cropped. Add
will-change: transform during the swap, and skip the swap when the mover
has no transform of its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-03 09:59:56 +00:00
Miguel Ángel b84835c413 feat(studio-server): the preview records the kind and origin of each early error (#7041)
* feat(studio-server): the preview records the kind and origin of each early error

* fix(studio-server): write preview script values with U+2028/2029 and < escaped
2026-11-03 07:47:49 +00:00
Miguel Ángel 3eead5432d fix(core): audio no longer plays fast for a moment after Play (#7067)
* fix(core): audio no longer plays fast for a moment after Play

* refactor(core): split the audio start wait out of the clock read
2026-11-03 07:30:57 +00:00
Miguel Ángel 0f85af7c7f fix(core): preview playback lands where a seek does when a set interrupts a tween (#7061)
* fix(core): preview playback lands where a seek does when a set interrupts a tween

* test(core): the preview overlap fix holds under a nested time scale and fires each callback once

* style(core): shorten the overlap comment

* refactor(core): the overlap span names its fields and carries no comments
2026-11-03 04:16:09 +00:00
James RussoandClaude Opus 5.5 112ba53302 fix(studio): motion blur falls back for a WebGL canvas Studio reaches first (#7060)
* fix(core): motion blur notices a WebGL canvas the Studio page touched first

When a same-origin host page (Studio) reaches a preview element before the
scene does, for example through a MutationObserver record, Chrome gives that
element the host's prototype. The scene's getContext("webgl") then ran the
host's unpatched getContext, so the canvas never reached the accelerated
canvas set, capture motion blur stayed on, and the WebGL canvas froze during
preview playback.

The tracker now records a canvas in the set of the canvas's own window, and
installs itself in a same-origin parent too, compiled with the parent's own
Function. Cross-origin parents are skipped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(studio): track accelerated canvases in Studio's window, not the embedder's

The preview runtime no longer installs the canvas tracker in its parent
window. That ran on every preview boot, so any same-origin page embedding
the player got its getContext wrapped, and a host CSP without
unsafe-eval reported a violation on each load. Studio now installs the
tracker in its own window when its Player module loads, and core
exposes it at @hyperframes/core/page-motion-blur.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: keep the comment share of the touched files from rising

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-03 03:59:23 +00:00
Miguel Ángel eb3562966f feat(preview): an unplayable video shows its own frame while its copy is made (#6589)
* feat(preview): an unplayable video shows its own frame while its copy is made

* fix(preview): drop the comment on the copy's light flag

* fix(preview): shorten the still helper's doc comment

* test(preview): each proxy-wait test starts with no copy report left over

* fix(preview): cached still frames are swept with the proxy copies
2026-11-03 00:23:50 +00:00
Vance IngallsandClaude Opus 5.5 2bdc866c85 fix(core): a canvas in a blurred composition stays where it was laid out (#6975)
* fix(core): a canvas in a blurred composition stays where it was laid out

drawElementImage drops a nested canvas's layout offset unless the canvas
has a transform, so with capture blur on, `top: 200px` painted at 0. A zero
`translate` while captured is enough, and leaves `top`, the composition's
own `transform` and its `translate` alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): give the nested canvas rule no specificity

A class on a composition's canvas lost to the rule, so its own `translate`
was dropped while captured. Under `:where()` any composition rule wins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:41:01 -08:00
Vance IngallsandClaude Opus 5.5 01c0bf4962 fix(core): a motion blur worker starting mid-film keeps the right state (#6967)
* fix(core): a motion blur worker starting mid-film keeps the right state

The mover scan walks the whole film with callbacks live, then seeks back to
the worker's start. GSAP fires one-off calls in both directions, so a worker
starting between two calls took the later call's state and held it until the
next call. Going through 0 first gives every worker what frame 0 sees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): rewind in the mover scan, so the filter tier gets it too

Both tiers call scanMovers and then seek to the worker's start. Rewinding
inside the scan covers them both, including the rescan after a tier handoff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:40:25 -08:00
Miguel Ángel e8ffaa410f fix(core): preview recovers its WebGL scenes and effects after a lost context (#7028)
* fix(core): preview repaints and rebuilds effects after the GPU restores a lost WebGL context

* fix(core): a paused scene redraws and a playing one repaints effects after a context restore

* test(core): a paused scene is redrawn after a WebGL context restore

* test(core): give the restore redraw test a clip

* fix(core): shorten the context recovery header comment

* fix(core): rebuild effects only after every lost context has restored

* test(core): the restore rebuild waits while another context is still lost

* test(core): count context requests to show the rebuild waits

* test(core): the mock reports the lost flag the test sets
2026-11-02 17:47:44 +00:00
Miguel Ángel c25e092114 fix(core): Safari audio is ready at the cut, and a stuck one resyncs in 1 s (#7024)
* fix(core): WebKit audio seeks to its in-point before its cut, and a stuck one resyncs in 1 s

* fix(core): shorten the WebKit stuck-audio comment

* fix(core): let the stuck-audio constant name stand without a comment
2026-11-02 16:46:47 +00:00
Miguel Ángel 036e41fa09 fix(core): iPhone Safari keeps one connection per file for the take on screen and the next (#7012)
* fix(core): iPhone Safari keeps one connection per file for the take on screen and the next

* fix(core): drop the doc comments the comment ratchet counts in studioPreviewMark
2026-11-02 13:15:47 +00:00
Miguel Ángel aaa5b63f55 fix(core): keep media prefetch queue ordered (#6996)
* fix(core): keep media prefetch queue ordered

* fix(core): refresh queued prefetch priorities on timing edits
2026-11-02 09:24:45 +00:00
Miguel Ángel 26d6e3eef1 fix(engine): a scene that has ended gives up its WebGL context so the next scene is not slowed (#6984)
* fix(engine): a scene that has ended gives up its WebGL context so the next scene is not slowed

* build(engine): raise the engine package size budget for the WebGL release module

* chore(engine): shorten the WebGL release comment

* fix(engine): a scene gives up its WebGL context only when the runtime says its window ended

* chore(engine): drop a comment the type already says

* chore(engine): shorten a comment line
2026-11-02 07:50:16 +00:00
Miguel Ángel e5b10f8bc2 feat(core): a heavy clip's lighter copy can be made ahead of time (#6982)
* feat(core): make a heavy clip's lighter copy ahead of time when the host asks

* feat(core): keep the comment share of the proxy transcoder where it was

* feat(core): split the copy-ahead choice so each part stays simple

* feat(core): the preview route passes the copy's urgency as the proxy options expect
2026-11-02 07:15:45 +00:00
Miguel Ángel 6bbda219bb fix(core): a smaller preview copy nobody wants any more no longer swaps in (#6973)
* fix(core): a smaller preview copy nobody wants any more no longer swaps in

* refactor(core): name the dropped copy instead of commenting it
2026-11-02 04:41:07 +00:00
Miguel Ángel 8519f28363 fix(core): a preview video short of data holds the film instead of looping snippets (#6974)
* fix(core): a preview video short of data holds the film instead of looping snippets

* fix(core): keep the comment share of the runtime files where it was
2026-11-02 04:14:40 +00:00
Miguel Ángel de6ec3811e fix: cache paths follow HYPERFRAMES_CACHE_DIR (#6716)
* fix: cache paths follow HYPERFRAMES_CACHE_DIR

Every place HyperFrames keeps downloaded or built files now takes its
folder from one helper, hyperframesCacheDir() in @hyperframes/core. The
default stays ~/.cache/hyperframes; HYPERFRAMES_CACHE_DIR moves it.

* fix: register the cache-dir subpath for bun and node, and build inspector from core source

* test: the cache-dir override test uses a folder that is absolute on every platform

* fix: the opencv cache follows HYPERFRAMES_CACHE_DIR

* fix(cli): npm installs run in their staging dir, so no cache path reaches a command line

* fix(cli): whisper.cpp is cloned inside its build dir, so no cache path is a git argument

* test(cli): the whisper clone test finds no system whisper on Windows either
2026-11-01 22:44:11 +00:00
Miguel Ángel 9284e69fff fix(core): accept 65-point .cube LUTs, the size grading apps export (#6921)
* fix(core): accept 65-point .cube LUTs, the size grading apps export

* fix(core): shrink LUTs wider than the GPU texture limit instead of rendering black
2026-11-01 20:46:08 +00:00
Miguel Ángel 89a5953161 fix(core): music keeps playing while a preview video swaps to a new copy (#6937)
* fix(core): music keeps playing while a preview video swaps to a new copy

* refactor(core): drop a comment the constant's name already says
2026-11-01 16:55:53 +00:00
Miguel Ángel 6b3e7a7a95 fix(cli): check and snapshot free video clips the playhead left, halving memory on cut films (#6929)
* fix(cli): check and snapshot free video clips the playhead left, halving memory on cut films

* fix(core): drop the releaseMedia doc comment that raised the file's comment share
2026-11-01 14:50:41 +00:00
Miguel Ángel af0d68d612 fix(core): a resized preview video keeps its picture while its new copy loads (#6922)
* fix(core): a resized preview video keeps its picture while its new copy loads

Swapping a video to a copy made for its new size emptied it until the copy
decoded, so the preview showed black for a moment. The swap now shows a still
of the frame it had, as the video's poster, until the new copy can draw.

* fix(core): a held preview still is let go when no file loads back

* chore(core): raise the packed size budget for the held preview still
2026-11-01 12:00:26 +00:00
Miguel Ángel e181d34edc feat(core): WebKit plays bare audio from a decoded buffer on the audio clock (#6912)
* feat(core): WebKit plays bare audio from a decoded buffer on the audio clock

* fix(core): a handed-over WebKit track goes back to its element when the speed changes
2026-11-01 03:53:30 +00:00
Miguel Ángel 2c1a10f3ad fix(core): Safari keeps playing edits' audio without seeks or rate changes (#6888)
* test(player): play the WebKit audio edits in real Safari on a macOS runner

* fix(core): Safari keeps playing edits' audio without seeks or rate changes

* fix(core): keep the 0.1 s audio sync bound outside WebKit

* fix(core): restore main's audio steering outside WebKit

* fix(core): tell WebKit from the test DOM's user agent

* fix(core): name WebKit's audio start lag instead of commenting it

* fix(core): count embedded WKWebViews as WebKit for audio sync
2026-11-01 03:08:27 +00:00
Miguel Ángel bba3676a37 feat(core): the playback health reading says which clock the film followed (#6906)
* feat(core): the playback health reading says which clock the film followed

* style(core): format the clock's new reading

* refactor(core): let the clock's names say what the comments said
2026-10-31 23:29:33 +00:00
Miguel Ángel 88f9ae8468 fix(core): a page saved as UTF-16 bundles as its text, not NUL bytes (#6580)
* fix(core): a page saved as UTF-16 bundles as its text, not NUL bytes

* fix(core): render, lint and the asset reader decode UTF-16 pages too
2026-10-31 21:02:41 +00:00
Miguel Ángel 1e5413f34c fix(core): a stuck seek in check or snapshot fails named before the protocol timeout (#6596)
* fix(core): a seek that never settles ends after 30 s and names what it waits on

* fix(core): keep the seek-wait comments within the comment limits

* fix(core): only a caller's limit ends a seek wait; check and snapshot end it before their timeout
2026-10-31 16:48:36 +00:00
Miguel Ángel 5c92fb0776 fix(core): style a sub-composition root written as #root[data-composition-id] (#6843)
* fix(core): style a sub-composition root written as #root[data-composition-id]

* fix(core): keep an anonymous host's root styled when #root[data-composition-id] is scoped

* fix(core): scope a compounded authored root as a selector list so pseudo-elements stay valid
2026-10-31 16:46:25 +00:00
Miguel Ángel 0c16afd949 fix(core): Safari stops chopping audio by seeking it over and over during playback (#6854)
* fix(core): Safari stops chopping audio by seeking it over and over during playback

* test(player): the WebKit playbench also counts audio seeks and frozen audio

* fix(core): audio still catching up is seeked once its lag passes hard sync
2026-10-31 16:12:24 +00:00
Miguel Ángel 2bd8cd95cb refactor(core): split the frozen-picture judgement so it stays under the complexity limit (#6870) 2026-10-31 02:13:23 +00:00
Vance IngallsandClaude Opus 5.5 5e4f5aefb5 fix(core): motion blur preview no longer slows down later in the timeline (#6865)
* fix(core): motion blur preview no longer slows down later in the timeline

With data-motion-blur-events="on", which Studio's Subtle and Strong write,
both blur tiers ran timeline callbacks at every per-frame pose read. Each
frame's reads step back from half a shutter ahead to the frame, so a
callback that keeps state and replays from zero on a backward seek (a
ripple simulation, for example) redid its whole history most frames, and
the cost grew with the playhead.

The mover scan still runs callbacks, so motion set from a render(t)
callback is still found. With events on, it also scans once without them,
and the per-frame reads run callbacks only when the two scans disagree.
The eventful scan runs first, so tweens take their start values after the
callbacks before them and a one-off call fires as often as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): keep the motion blur comments to the invariant

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): motion blur reads run callbacks when a callback moves any box

Comparing which elements each scan finds missed a callback that pans a
mover's ancestor while a tween also changes that ancestor: both scans
found the same movers and the same changing elements, so the reads left
the pan out. The scans now compare every changing element's box at each
step, and the reads run callbacks when any step differs.

Raises @hyperframes/core's unpacked size budget to the measured size plus
the usual 5%: main already sat 0.98% over the old budget, and this change
adds 1,232 bytes, which tipped it past the 1% tolerance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): silent motion blur reads land back on the frame as its own seek left it

After silent pose reads, the restore seek back to the frame left GSAP in a
different state than the frame's own eventful seek did:

- A step exactly on the frame (a .call() or set) was re-armed, so the next
  frame fired it again. Calls fired 2-5 times per crossing, and a toggle
  could end inverted.
- In renders, which keep no GSAP structure, landing back on the frame from
  later skipped same-time steps' authored order, so the frame showed the
  first of two sets instead of the last.

Events-off blur always read silently and had both. This PR moves events-on
roots without callback-driven motion (the Studio default) onto that path,
so both are fixed here.

The host's new holdFrame snapshots every landing mark on the root and scene
timelines before the reads, and its restore seeks back silently, redraws in
authored order where there is no structure, and puts the marks back. Each
call now fires once per frame crossing, as without blur, and same-time sets
land as authored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): a held render frame draws its scenes first, so the root lands last

holdFrame redrew the root and then the scenes, the reverse of the frame's
own seek. When a scene and the root tween one property, the frame landed
on the scene's value instead of the root's.

The hold and landing move into the GSAP adapter as holdFrameMarks, which
takes the scenes and the root separately, so the order lives in one place
a real-GSAP test can pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:21:27 -07:00
Miguel Ángel 443ded4261 fix(player): preview voice no longer goes silent while other tracks play (#6859)
* fix(core): audio that decodes short plays from its element in preview

* fix(core): keep the decoded copy when the element's duration is unknown

* fix(player): preview pauses with Buffering until every track due can play

* fix(core): keep the transport awake while it buffers

* fix(core): wait only for tracks the clock would also treat as playable

* test(core): buffering fixture pauses like a browser, with a pause event

* fix(core): hold for a clip only after media sync has asked it to play

* test(core): hold lands on the frame a slow tick skips into a clip with no data

* fix(core): a held clip counts as ready once it has data buffered ahead
2026-10-30 21:25:28 +00:00
Miguel Ángel 359b6c8a38 feat(core): playback health reports how long a video on screen showed no moving picture (#6856)
* feat(core): playback health reports how long a video on screen showed no moving picture

* fix(core): a frozen picture is judged by the clip's own timing, not the film's

* style(core): shorten a test comment under the line limit

* fix(core): the frozen-picture check reads a video from any realm

* refactor(core): cut the frozen-picture comments the comment ratchet counts
2026-10-30 18:48:25 +00:00
Miguel Ángel aafbd96b77 fix(core): same-time sets at 0 land in authored order on the first frame (#6851) 2026-10-30 18:29:40 +00:00
Muzzy5150 64d190e34f fix(core): prevent Figma motion property key collisions (#6800) 2026-10-30 16:04:03 +00:00
Akshar Patel fc3d0a5fce fix(core): load responsive images from their scene (#6399) 2026-10-30 16:03:37 +00:00
Miguel Ángel 07c714ce9b fix(core): the first cut after Play starts on time instead of running ahead (#6794)
* fix(core): the first cut after Play starts on time instead of running ahead

* fix(core): learn half the start delay from Play so the first cut keeps its last frame
2026-10-30 08:24:36 +00:00
Miguel Ángel d83f276743 perf(studio): films with thousands of timed clips stop re-reading every clip each frame (#6805)
* perf(studio): films with thousands of timed clips stop re-reading every clip each frame

* perf(studio): drop the added comment lines the ratchet counts
2026-10-30 07:27:26 +00:00
Miguel Ángel 9fd90d23c4 fix(core): long cut-list edits play every cut without dropped or late frames (#6785)
* fix(core): a video plays to the last frame before its out-point instead of stopping a frame early

* fix(core): a dropped frame before an out-point no longer lets the out-point's frame show

* perf(core): a video edit with no animation timeline stops rewriting every clip each frame

* refactor(core): drop the comments the comment ratchet counts

* fix(core): a clip that has fully downloaded stays loaded and plays on time

* refactor(core): drop the comments the comment ratchet counts
2026-10-30 05:01:56 +00:00
Miguel Ángel 471d60a8ac fix(core): the playhead keeps moving at a cut while the incoming take has not started (#6768)
* fix(core): the playhead keeps moving at a cut while the incoming take has not started

* fix(core): shorten a comment in the clock

* fix(player): keep the WebKit stops script out of the type check

* fix(core): cut two comments from the clock
2026-10-30 02:25:26 +00:00
Miguel Ángel c5a52037c9 fix(core): a still sub-composition's data-no-timeline on its template root skips the timeline wait (#6762)
* fix(core): a still sub-composition's data-no-timeline on its template root skips the timeline wait

* chore(core): drop a comment the copy already says
2026-10-30 01:48:17 +00:00
Miguel Ángel 93f3d3dcbb fix(core): the preview's scene window settles when a scene can't unmount instead of hanging the page (#6740)
* fix(core): the preview's scene window settles when a scene can't unmount instead of hanging the page

* refactor(core): name the scene window's stuck step instead of commenting it
2026-10-29 12:04:12 +00:00
Miguel Ángel 71f716a91b fix(core): telemetry redaction covers folders with spaces in any script (#6734)
* fix(core): telemetry redaction covers folders with spaces in any script

* style(core): shorten the redaction folder note

* fix(core): spaced folders without a root redact only before a file name

* style(core): drop the redaction note that described the leak as intended

* style(core): two fewer comment lines in the redactor

* fix(core): bound the spaced-folder match so long lines stay linear
2026-10-29 10:19:12 +00:00
Miguel Ángel 48ba0e5b2b fix(player): iPhone Safari keeps every take's sound after the first seconds of an edit (#6724)
* fix(player): iPhone Safari keeps every take's sound after the first seconds of an edit

* fix(player): shorten the priming comment

* fix(player): prime iPhone takes only, and only unmuted ones

* fix(player): shorten a comment

* fix(player): skip only clips authored muted, and drop a doc comment
2026-10-29 07:56:37 +00:00
Miguel Ángel cf15892425 fix(cli): a snapshot still shows what playback shows, whatever other times are asked for (#6720)
* fix(cli): a snapshot still shows what playback shows, whatever other times are asked for

* test(cli): pin that still-capture pages ask for the skipped-frame replay
2026-10-29 06:44:51 +00:00
Miguel Ángel fa9acbd739 fix(core): a multi-worker or chunked render lands every frame where an in-order render does (#6707)
* fix(core): a multi-worker or chunked render lands every frame where an in-order render does

* fix(core): replay only where tweens overlap, and from any off-grid scene time

* fix(core): a film with paused scenes replays only where its tweens overlap
2026-10-29 04:58:34 +00:00
Jerrai 4fa5f54519 fix(cli): audio windows read beats off the whole clip; beats snap to the signal and mark silence (#6692)
* fix(cli): audio windows read beats off the whole clip; beats snap to the signal and mark silence

A film query that covers part of a clip measures beats, downbeats, bpm and sections on the
whole clip and cuts them to the window, as #6653 did for kind, so a window under 6 s still
has its beats.

One step after every provider ladder moves a provider's beats and downbeats onto the
signal's own beats within a quarter beat (the snapping api did alone) and marks each with
`silent`, true where the beat falls in silence. Beats are marked, never dropped; the JSON
change is the additive `silent` array.

— Jerrai

* fix(cli): beats with no pulse are unheard; a window with none answers no steady beat

Review fixes for #6692:
- The per-beat flag is now `unheard` (renamed from `silent`, not released): true in silence
  or where no onset falls within 50 ms of the beat, so hiss, room tone or a held pad no
  longer pass as beats.
- A window keeps the whole clip's beats only when at least two of its beats are heard;
  otherwise it answers "no steady beat" as before, with no downbeats and no bpm.
- A window's bpm is the tempo of its heard beats, never the whole clip's.
- Comment lines trimmed so analyze.ts, run.ts and api.ts are at or under main's share.

— Jerrai

* fix(cli): a beat is unheard only where two beats in a row have no onset

Scored on the ground-truth set, flagging every onset-less beat also flagged heard beats the
onset detector misses: the drop downbeat after a snare roll, single beats under narration,
quiet beats late in a fade. A pulse gap now needs two onset-less beats in a row; hiss, room
tone and held pads span many beats and stay flagged, and silence is flagged as before.

— Jerrai

* fix(cli): a window with no pulse on the clip's grid reads its own beats, as before

Genesis found a window over a section far under the clip's loudest part answered "no steady
beat" where main, reading the window alone, found every beat: the clip's grid and onsets
skip a section that quiet (from about 40 dB under, for clicks). When a window keeps fewer than
two heard beats from the clip's grid, its beats, downbeats and bpm now come from the window
alone, as they did on main. Hiss still answers "no steady beat" on its own.

The hiss test's noise() repeats every 10,466 samples (0.22 s at 48 kHz), which the window's
own reading took for a pulse; that test now uses a xorshift generator.

— Jerrai

* fix(audio): unheard comes from level-free rhythm evidence, so quiet music stays heard

A beat was flagged by an absolute -50 dBFS silence and by onsets under 10% of the clip's
loudest hit, so music 30 dB or more under the loudest part read as unheard. Now:
- core localBeatEvidence reads the cached standardised onset curve over the 6 s around each
  beat: the tracker's steady-beat confidence there (clarity at the local beat lag times the
  beat's own lift) and the beat's lift over the span's mean.
- A beat is unheard in digital silence (-80 dBFS), or when its span keeps no steady beat
  (confidence under 0.15) and its own onset does not stand out (lift under 1.5). Lift alone
  keeps beats under narration heard, where syllables lower the span's clarity.
- A window keeps the clip's beats when at least two, and at least half, of them are heard.

— Jerrai
2026-10-29 01:52:48 +00:00
Miguel Ángel 9830059bdb fix(engine): downloads refuse hosts that resolve to private addresses (#6571)
* fix(engine): downloads refuse hosts that resolve to private addresses

* fix(core): shorten the env proxy doc comment

* test(producer): remote-media tests stub the download fetch seam
2026-10-28 20:08:50 +00:00