mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
d65eefad5ed351e9d36d2caeb6049878be0aba85
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
95837a3df7 |
fix(core): keep the colour grade on a graded video's first rendered frame (#5575)
* fix(core): keep the colour grade on a graded video's first rendered frame A graded clip rendered ungraded for one frame wherever a capture session first drew it: the clip's own first frame, a parallel worker's first frame inside it, or a capture segment's first frame inside it. The grade dropped the clip while its injected frame was still decoding, so the redraw after decode had nothing to draw. Part of #5548 * chore: pin the producer test-classification hash and drop an added comment |
||
|
|
e1925d4317 |
feat(audio): composition master bus (data-fx-chain on the root) (#5420)
* feat(audio): composition master bus (data-fx-chain on the root) A clip can belong to only one audio group, so there was no way to put one effect, such as a limiter, across the whole mix. The data-fx-chain on the top-level composition root now runs once over the sum of every clip and every group, after each group's own chain and fader. No new attribute: a sub-composition root's data-fx-chain was never applied to anything and still is not. Render: when a master chain exists, mixAudioTracks writes a float WAV instead of AAC, applyAudioFxChain runs the chain over it, and ffmpeg encodes AAC last, so the chain reads unclipped samples. An unreadable chain is a mix failure, as for a group. Preview: the transport splices the chain between the clips' sum and the monitor, re-anchored per play generation and watched for edits. Only the top-level root is read. No master automation: the root's data-automation is not read as the bus's. No lint rule: data-fx-chain is valid on clips, groups and roots, and the one place it does nothing (a sub-composition root) has always been silent. — Jerrai * fix(core): preview routes every audible video through the master bus A root data-fx-chain is attached only inside the Web Audio graph, but a plainly audible <video> joined that graph only for its own chain, group, automation or a volume over 1. So preview limited the music and left the talking head dry, and a composition whose only audio is video got no master chain at all, while export sums every audible element into the master. A master chain on the top-level composition root is now a reason to join, the same as a video's own chain. Elements under a root with no chain keep native output. Sub-composition roots are still ignored. — Jerrai * fix(audio): master bus review round 2 (temp dir, tests, docs, Studio meter) - audioMixer: a fatal FX error in the master pass or a group bus left the work directory (the full float sum, about 1.4 GB an hour) on disk. The final mix now runs in try/finally and the group rethrow removes it first. - Tests: the "once over the sum" case now uses a limiter then a gain, so a per-clip chain (-10.4) and a doubled one (-18) both fail it; a new case proves the sum stays float until the chain reads it; the fader case uses a group volume of 0.5 so the fader's position is exercised; the work-dir removal is covered for both paths. - Docs: "Three rules exist" (the master-bus lint rule is gone), a bad master chain stops the render with audio_processing_failed, a sub-composition file opened on its own has its root chain as the master bus, and a seek clears a group or master limiter. - Studio: TimelineElement.masterChain carries the root's data-fx-chain, so the program meter shows for a video-only composition whose only audio routing is the master chain, as preview now plays it. - Comment blocks over four lines cut; webAudioTransport.setRate split so its CRAP score is under the Fallow threshold. — Jerrai * fix(audio): master chain reaches tracks the render adds; honest plain-limiter copy - Master chain: it was carried on each parsed AudioElement, so a track the producer adds itself (appendAutoDetectedVideoAudio, the browser-discovered audio in probeStage) never had it, and a composition whose only audio was such a track exported with no master chain, no ceiling and no warning. It is now a composition property: parseMasterFxChain(html) at compile, carried on CompiledComposition and CompositionMetadata, passed to processCompositionAudio by runAudioStage (in-process and distributed plan). AudioElement.masterFxChain is removed so there is one source. Each forward (compile stage, local orchestrator, distributed plan) has a test that fails when it is dropped. - Plain limiter: it follows the level with no lookahead and Level after is applied past the ceiling, so it cannot promise "nothing gets past" or "never clips". Studio copy (description, panel text, knob label, summary) and the hyperframes-audio skill now say a fast or inter-sample peak can pass it and point to the true-peak limiter for a delivery limit. A copy test guards it. — Jerrai * fix(producer): keep master bus comments within the ratchet; run the plan test with the ffmpeg tests Drop three one-line field comments that raised their files' comment share, and list plan.masterBus.test.ts with the integration tests because plan() reads the ffmpeg version. — Jerrai * chore(ci): repin the producer test-classification source in the reachability map The reachability map pins the hash of test-classification.mjs, which now lists one more integration test. The map's producer lane command already covers every test under src, in both the unit and integration matrix lanes. — Jerrai |
||
|
|
e23b3a3e31 |
fix(engine): copy border-width/style/color onto the replacement video frame (#3993)
* fix(engine): copy border-width/style/color onto the replacement video frame
injectVideoFramesBatch substitutes each <video> with a sibling <img> holding
an extracted still frame, copying an allow-listed set of CSS properties from
the video's computed style onto the img. border-width/border-style/
border-color were absent from that allow-list, so any border authored
directly on a <video> never reached the replacement image and disappeared
from render/snapshot output. border-radius and clip-path were already on the
list and already clip a replaced element's content correctly without
needing overflow:hidden, confirmed by a real-Chromium test that exercises
the actual capture path and reads real screenshot pixels.
Also corrected a stale comment in frameCapture.ts claiming
detectCssEffectRisk can return "clip-path" as a risk value -- it can't; that
string only ever comes from a separate, animation-only at-risk-props gate
further down the same file.
Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
* test(producer): regenerate style-9-prod golden for the border fix
style-9-prod's video has an authored 8px border + 16px border-radius
(design_review.md calls out the "floating" look) that never rendered before
this fix, so its committed golden baked in the pre-fix (borderless) output.
Regenerated via `tsx src/regression-harness.ts style-9-prod --update`;
verified the diff is exactly the border/radius becoming visible (extracted
frames before/after) and the fixture passes its own visual/audio checks
again (100/100 checkpoints, correlation 1.000).
Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
* fix(engine): measure the video's box before styling its replacement frame
injectVideoFramesBatch measured the video's used box (offsetLeft/Top/
Width/Height) after creating, inserting, and styling the replacement
<img> sibling -- once that copy loop started carrying border-width
(added for the border-visibility fix), the freshly-inserted, still
in-flow, now-bordered <img> competed for space in a flex row before
the video's own box was ever read. In a flex-centered layout (video
width:100%, sharing a row with the img sibling), that shrank the
measured video box by the exact width of the border, so the img ended
up sized from the video's post-shrink box instead of its authored one.
Moved the measurement to before the <img> is created/inserted/styled
at all, and set img.style.boxSizing = "border-box" explicitly after
the style-copy loop, since offsetWidth/offsetHeight (and the
getBoundingClientRect fallback) are always a border-box measurement
regardless of what box-sizing value that loop copies from the video's
own computed style.
Added a flex-centered regression test asserting the replacement img's
box stays identical to the video's box; confirmed it fails with the
old measurement order (a 16px border shrinks a 500px box to 484px) and
passes with the fix. Regenerated the one fixture (style-9-prod) whose
committed golden had baked in the shrunk-frame geometry.
Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
* refactor(engine): consolidate video-box measurement into one object
Follow-up to
|
||
|
|
3b89bfe8a7 |
fix(engine): motion blur works over video by holding each video frame across samples (#5150)
* fix(engine): motion blur works over video by holding each video frame across samples Each output frame's injected video frame is now held for every sub-frame sample and probe of that frame, so footage stays as shot and only the layers over it blur. The refusal for sessions with a before-capture hook is removed. Fixes #5144 * fix(engine): trim held-video comments, pin the new test-classification hash * fix(engine): a blurred frame at a sub-composition cut keeps its video Each sample now decides which videos show at its own time, like every other layer, and only a video on screen at the output frame's time keeps that frame's picture. Before, the visible set was taken at the frame's time while sub-composition hosts were hidden at the sample's time, so the samples just before a cut showed no video and the cut frame dipped toward black. The before-capture hook takes the held time as an optional third argument, and the early injection is gone: the GPU re-render now always runs at the page's own time. * style(engine): one doc comment for the before-capture hook * fix(engine): a video animating its own style stays in step when blurred The injector copies a video's own style only when its frame changes, so the first injection of each blurred frame now runs at the frame's own time before the samples. A video sliding by its own transform no longer sits a quarter frame behind with blur on. * test(producer): a two-line header for the motion blur over video test * test(producer): reach the cut fixture by path, not a URL fallow reads as an import |
||
|
|
04736006b1 |
test(producer): unit tests never reach the network (#5135)
* test(producer): unit tests never reach the network * test(producer): the unit lane refuses sockets to other hosts, not only fetch * test(producer): refuse outside hosts at the lookup, not by parsing socket calls * test(producer): keep dns.lookup promisify-compatible under the no-network guard |
||
|
|
39a693c0a4 |
fix(engine): a render fails when a composition's own script throws before its timeline binds (#5033)
* fix(engine): a render fails when a composition script throws before its timeline binds * fix(engine): an unrelated page error no longer fails a render whose timeline registers late * fix(engine): only the composition's own scripts fail a render, local or distributed * style(engine): format the script failure change * fix(engine): a third-party browser rejection no longer fails a render * refactor(engine): one page URL for navigation and error attribution * fix(engine): a rejection after a hash change or in an iframe no longer fails a render * fix(engine): a widget's error no longer fails a render once its script is inlined * fix(core): name injected and inlined scripts without building code from library input * fix(engine): a third-party embed's eval or inserted script no longer fails a render * fix(producer): a widget's inline handler or a sub-composition no longer misreports a render * refactor(engine): read each script response once and keep comments within 120 characters * fix(engine): a scene that throws after registering its timeline no longer cuts a render short * fix(engine): a composition script failure names the error and its real cause * fix(engine): a failed VFX chain fails the render at once instead of shipping a missing layer * fix(core): a VFX layer that is empty on one frame renders empty instead of failing or going stale * fix(core): a VFX layer that grows from zero size renders instead of staying missing * fix(core): a VFX layer hidden or late while empty renders once it has a size * test(producer): cover a VFX layer that is 0×0 under a host that starts later * test(producer): pin both VFX 0×0 marks, with a GSAP-shaped wipe and a real late host * test(producer): a late VFX host with a GSAP wipe catches a walk that skips hidden hosts |
||
|
|
b3d2b82fe9 |
feat(core): the renderer's font pipeline is importable from @hyperframes/core/fonts/embed (#5041)
* feat(core): the renderer's font pipeline is importable from @hyperframes/core/fonts/embed * style(core): format the font pipeline and state the bundled-subset invariant * test(producer): repin the test lane map after the font tests moved to core * fix(core): images build fonts once, CLI bundles one pipeline, font tests pass on Windows * test(core): font retry tests get a budget no slow machine can exhaust * test(core): the Retry-After budget test fails when the budget is ignored |
||
|
|
16871a1796 |
fix: preview and bundled scripts run where and when the author put them (#5014)
* fix(core): bundled local scripts keep their own place, defer and async The bundler joined every local <script src> into one inline script at the first one's position. That dropped defer and async (a deferred main.js ran before a deferred CDN gsap), moved later files ahead of CDN scripts between them, and let one file's error stop the rest. Each local script is now inlined where it stands with its attributes; a deferred or async one keeps its tag with its file as a data: URL. Inline-script merging also no longer folds in nomodule scripts, which dropped the attribute. * fix: preview scripts run after the runtime when the bundler fails, and inlined files are escaped - When the bundler returns nothing or throws, the preview places the runtime where the bundled page does (end of head) instead of after the composition's scripts or not at all; core's placement moves to insertRuntimeTag. - Every HTML-spec JavaScript MIME type counts as a classic script for merging and the web-font gate. - Inlined local files and merged runs are escaped with escapeInlineScriptSource. * fix: deferred local scripts run late without data: URLs, and authored runtime reads survive - Local defer/async scripts are inlined in their own tag with the attribute kept; the font-gate runner (and its old-runtime fallback) runs an inline classic defer script after the classic scripts, in document order, so a page policy that allows inline scripts but not data: still runs them. - stripEmbeddedRuntimeScripts matches the bootstrap attribute and runtime file names on the tag, not runtime words in an authored script's text. - The preview applies its runtime on every path, so an adapter page without one still gets it; the inline escaper writes <!-- as \x3C!--, valid in a unicode regex. * fix: a deferred inline script waits for the deferred CDN script before it to load * fix: an authored script that mentions the runtime marker no longer stops the runtime being added * fix: only inlined deferred files run late, head ones included, and renders skip a CDN runtime link * fix: deferred head files run as before, and one selector owns the deferred-file rule * fix: a local file's legacy <!-- comment no longer breaks its merged run, and escapes keep case * fix: a local file inlined on its own keeps working with a legacy <!-- comment * test(core): a local non-JavaScript file keeps its text as written |
||
|
|
29ded9b0fa |
chore(deps): update github-actions (#3934)
* chore(deps): update github-actions * ci: re-pin producer test job hash after the checkout action bump --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |
||
|
|
f7ef18dca8 |
fix(producer): renders leave no captured frames behind when interrupted (#4694)
Ctrl+C during a render left its whole work dir of captured frames (and a staging dir) in the renders folder. Puppeteer's default SIGINT handler kills Chrome and exits the process before the render's own cancel path can remove them. The engine keeps that default unless the host owns Ctrl+C; the CLI's render cancellation scope claims it while a render runs, so the render aborts and cleans up after itself. A render killed outright (SIGKILL, crash) cannot clean up, so each work dir and staging dir now records its owner process (pid, hostname, boot, and on Linux the pid namespace), and the next render into the same folder removes the ones whose owner has exited. Cancelling a render while its browsers start no longer leaves anything running that kept a batch render alive after Ctrl+C: the BeginFrame warm-up loop, the probe browser and file server, or a capture browser whose launch finished after the cancel (now closed before the worker settles, since Puppeteer's exit hook skips every other live browser). |
||
|
|
5383e486bb |
fix(producer): gif renders survive rgb frames among transparent ones (#4610)
* fix(producer): gif renders survive rgb frames among transparent ones * test(producer): run the gif mixed-frame encode test in the ffmpeg lane * fix(producer): gif encode test passes the argv, reachability and comment checks * fix(producer): transparent gifs clear every frame so an opaque frame never lingers * fix(producer): only gifs that turn translucent after an opaque frame get whole cleared frames * fix(core): plain text keeps its own gif control block; gif encode failures share one shape |
||
|
|
2b1bb8b731 |
test(cli): CLI tests run with a home folder of their own, never the user's (#4587)
* test(cli): CLI tests run with a home folder of their own, never the user's * style(cli): format the test home check * test(cli): the skills test lane also runs with a home folder of its own * style(cli): format the test home check * ci: the test reachability map follows the skills lane's new command |
||
|
|
29fc95395d |
fix(skills): keep script imports inside installed skill directories (#4356)
* fix(skills): keep script imports inside installed skill directories * refactor(skills): simplify standalone module checks * fix(skills): carry the brand-logo lookup into the media-use copy of media-fetch |
||
|
|
922be55e55 | fix(skills): preserve captured font subsets (#4358) | ||
|
|
b32b9a7dab |
feat(runtime): data-vfx-chain — a seek-deterministic per-pixel effect element (#4306)
* feat(core): data-vfx-chain defs and chain parsing (mirrors data-fx-chain)
Mirrors audioFx.ts's shape for the per-pixel VFX chain: a versioned
JSON chain attribute (data-vfx-chain), a def registry with typed
params (number/enum/bool/ref), and parse/serialize/normalize helpers.
HF_VFX holds one def, fractal-noise, with a placeholder frag; the
kernel and its consumers (packages/core/src/runtime/vfx.ts) land in
Task 1.2/1.3 of the vfx-chain-runtime plan, so vfx.ts is added to
.fallowrc.jsonc's entry list for now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat(runtime): paint data-vfx-chain canvases on every seek
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(vfx): fractal-noise kernel (Basic/fBm approximation) with CPU reference
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(vfx): self capture through layoutsubtree with the page-composite readiness protocol
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(vfx): wave-warp kernel (Sine)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(vfx): displacement-map kernel (self-referential map)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(vfx): chain order is nesting order; preceding CSS effects are captured
...and following ones apply outside. The plan's one-line message for Task 2.4
is 105 characters; commitlint caps the header at 100, so the tail moved here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(vfx): seek determinism and CPU-reference PSNR for fractal-noise
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(vfx): size the output canvas from the host's layout box, not its transformed AABB
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat(telemetry): vfx node count, capture class and types on render_complete
Threads vfxNodeCount, vfxCapture and vfxTypes from a static scan of the
compiled composition's data-vfx-chain hosts (mirrors the existing
compositionElementCount/colorGradingCount scan) through
RenderPerfSummary.drawElement into the render_complete event, next to
de_capture_mode. Exports chainCapture/parseVfxChain/HfVfxCapture from
@hyperframes/core's index for the producer to consume. The PostHog
dashboard tile is not created here — left for the coordinator.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(vfx): data-vfx-chain guide — element shape, capture table, Studio flag, known constraints
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(vfx): register chain hosts that arrive with a sub-composition mount
`initVfx(document.body, fps)` ran once, synchronously, three lines after
`void loadExternalCompositions(...)` — so every `data-vfx-chain` host that
enters the DOM only when a `data-composition-src` mount resolves was never
registered. An unregistered host paints nothing and reports nothing: no
`[HyperFrames] composition script error`, the `.hf-vfx-out` canvas keeps its
300x150 default, and `paintVfx` never even arms the page-composite flag.
Measured on a real multi-file export served raw: `#main-l5-text`'s output
canvas stayed 300x150 and `window.__timelines` never gained the sub-comp.
Re-scan in the loader's `.finally`, before `maybePublishRenderReady()`, so
registration precedes readiness. `initVfx` now releases the outgoing
registry's programs, capture textures and ping-pong targets first — the
output canvas hands back the same WebGL2 context on a re-scan, so the
previous pass's GL objects would otherwise leak.
Two browser cases in `vfxDeterminism.test.ts`, both against the real runtime
bundle: a host whose `.hf-vfx-in` IS the mount (already worked — the mount
replaces the wrapper's children, not the wrapper, so the registered source
node survives), and a host that arrives INSIDE a mounted sub-composition
(failed with `armed: false` and no error; passes now).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(vfx): runtime tsconfig typecheck passes
Under noUncheckedIndexedAccess, resolveCaptureSource's null return
widened VfxEntry.src past its VfxCaptureSource | undefined type; the
function is internal-only, so its return type moves to
VfxCaptureSource | undefined instead. Indexing PingPong.framebuffers
by a non-literal index produced | undefined against
WebGLFramebuffer | null, coalesced to null. No behavior change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(vfx): structural DOM predicates instead of realm-bound instanceof
vfx.ts checked `instanceof HTMLCanvasElement`/`HTMLElement` directly, which
returns false for inner-realm nodes when the Studio previews across an
iframe realm. domRealm.ts had no canvas predicate yet, so this adds
isCanvasElement mirroring the existing isImageElement pattern, then swaps
all three vfx.ts checks to the structural predicates the lint guard requires.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(vfx): paint self-capture hosts under hyperframes snapshot, not only the engine path
`paintVfx(t, { engineMode: true })` armed the page-composite protocol and
returned, betting that every capturing chain runs under `frameCapture.ts`.
`hyperframes snapshot` (and check/compare/validate/layout, and Studio's
thumbnail capture) seek through the same `renderSeek`, never read
`__hf_page_composite_pending` and never call `__hf_page_composite_resolve` —
so a `self` chain painted nothing there, in silence, because its content
lives in a `layoutsubtree` canvas the browser never paints directly.
Engine mode now arms the preview-side capture as well and paints on whichever
completes first. A seek token stops a superseded async capture from repainting
a newer frame, and a registry-identity check stops it painting an entry
`initVfx` already released. `resolveVfxCapture` stays non-idempotent on
purpose: the engine resolves only after its own before-capture hooks have
mutated the DOM, so its capture is still the authoritative one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(vfx): an exporter-emitted .hf-vfx-out gets the box the spec fixes for it
findOrCreateOut adopted a canvas the exporter had already emitted without
applying the layout cssText, so it kept the default position:static;
display:inline and wrapped to the line AFTER the layer-wide .hf-vfx-src
canvas — painting a second, misplaced copy of the layer one full layer
height below the host. Measured on retro-wave #main-l6-text: .hf-vfx-src
rect top 315.1 h 93.0, .hf-vfx-out rect top 409.2, both style=null,
position static (ae-mcp findings Task 3.5b). Hosts with no emitted
out-canvas were never affected, which is why only this one host moved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(telemetry): vfx_node_count counts hosts, not chain nodes — rename to vfx_host_count
The field increments once per `data-vfx-chain` attribute occurrence, never
per node inside a chain's JSON, so the name promised something the scan does
not measure. Renames the internal `vfxNodeCount` and the wire property
`vfx_node_count` to `vfxHostCount`/`vfx_host_count` across the orchestrator
scan, the perf summary, the CLI render path and the telemetry event. No
dashboard consumes the field yet, so the wire rename costs nothing now and
would cost a migration later.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(vfx): join the preview capture to the seek-completion barrier, not race it
`paintVfx` fired `capturePreviewThenPaint` and forgot it, so nothing the CLI
awaits waited for a `self` chain's paint. Every snapshot-family command
(`snapshot`, `check`, `compare`, `validate`, `layout`) seeks through
`seekCompositionTimeline`, which drains `window.__hfWaitForSeekCompletion`
and then allows one settle race before screenshotting — and only the
`"hf-seek"` event's `waitUntil()` could put anything in that barrier. The
screenshot therefore landed on however many hosts happened to have finished,
which varied with host count and machine speed.
Extracts the "track, untrack on settle, retain the first rejection" block
`dispatch()` built inline into an exported `registerSeekCompletion`, and has
`paintVfx` register the preview capture through it. Also awaits the per-host
paints with `Promise.all` instead of sequentially: N hosts cost about two
animation frames now, not 2N.
Measured on the pre-fix bundle with two `self` hosts, both output canvases
read `[0,0,0,0]` at the moment the barrier returned; they painted only
because the settle race that follows happened to be long enough. Both tests
added here fail on that bundle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(vfx): report WebGL context loss loudly instead of silently no-op painting
One WebGL2 context per `data-vfx-chain` host, against a browser cap that is
commonly 16 and shared with `colorGrading.ts`'s per-element contexts. A lost
context makes every later call on it do nothing, per spec — so the chain kept
"painting" a blank canvas with no error, which is exactly what the module's
loud-failure contract exists to prevent.
Adds the `webglcontextlost` listener pair's first half, following
`colorGrading.ts`'s precedent: `preventDefault()`, mark the entry, report
once through `reportVfxError`. The entry is then skipped by `paintVfx`,
`capturePreviewThenPaint` and `resolveVfxCapture` — silently, since the one
loud report already happened and repeating it per frame is spam.
Restore is left unhandled on purpose, with the reasoning in the doc comment:
`initVfx`'s existing re-scan already rebuilds every GL object, and a second
narrower rebuild path for `webglcontextrestored` would duplicate it. The
shared-context redesign the review floated is likewise out of scope here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* perf(vfx): cache uniform locations at pass-build time instead of querying every paint
`setPassUniforms` asked the driver for `u_size`, `u_t`, `u_fps` and every
param's location on every paint of every pass, and `bindPass` did the same
for `u_src` on every bind. `buildPasses` already constructs each `VfxPass`
once at init, right after the program links, and a linked program's
locations never change — so they are resolved there now and read from
`pass.locations` in the hot path.
Same values, same `null`-means-optimised-out semantics (`gl.uniform*(null,
…)` is a defined no-op, which is what the per-frame query produced too), so
the existing vfx.test.ts and the browser determinism suite cover it
unchanged — both still green, the latter against a rebuilt runtime bundle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(vfx): bound the paint wait per host so a stalled compositor can't hang the seek barrier
`paintVfx` registers `capturePreviewThenPaint` into the shared seek-completion
barrier (
|
||
|
|
cff467a352 |
ci: select the remaining orphan tests in their owning runners (#4260)
* ci: select the remaining orphan tests in their owning runners * ci: provide FFmpeg for the catalog preview witness * ci: pass the installed Chrome path to catalog rendering * test: align creator capability checks with speed ramp support #4153 added supported speed ramps as rate lanes in data-automation. The previously unselected capability tests still required preprocessing. Require the documented rate-lane representation and its precedence over the constant playback rate, matching the runtime resolver. |
||
|
|
0be08f34a2 |
ci: ratchet test reachability across workflow filters (#4253)
* ci: ratchet test reachability across workflow filters * fix: fail closed on skipped detection and quoted selectors * refactor: separate reachability parsing decisions * fix: keep comments and runner prefixes out of reachability * refactor: bound reachability parser complexity * fix: reject conditional shell runner blocks * fix: reject conditional shell constructs in test routing |