Commit Graph
1446 Commits
Author SHA1 Message Date
Miguel Ángel 0c169e2c56 perf(producer): fast capture stays on for frames a blend mode does not touch (#6330) 2026-10-25 20:24:49 +00:00
Miguel Ángel d7efd809f6 feat(producer): record video frame extraction time in the chunk perf sidecar (#6329)
* feat(producer): record video frame extraction time in the chunk perf sidecar

* docs(producer): say videoExtractMs overlaps capture, not the boot
2026-10-25 19:52:49 +00:00
Miguel Ángel 70187a13b5 perf(core): the preview mounts and draws only the scenes near the playhead (#6189)
* perf(core): skip rendering preview scenes far from the playhead

* refactor(core): split the far-scene pass into small helpers

* perf(core): a first render reveals only the clips it reaches

* fix(core): reveal the clips holding first-rendered tweens, wherever their window is

* feat(core): a scene may load a project helper and wait for fonts and still swap

* perf(core): the preview mounts only the scenes near the playhead

* perf(core): a seek waits only for the scene it lands in; neighbours mount one per turn

* feat(studio): selecting a clip in an unmounted scene mounts that scene first

* fix(core): a source a play and a seek both need is fetched and decoded once

* fix(core): a scene mounting during play joins the mix without restarting what plays

* fix(studio): selecting a clip waits on a scene mount only in a preview that unmounts scenes

* fix(core): far scenes still mount after an edit, and an edit to a parked scene keeps its styles

* perf(core): web audio schedules a clip only as the playhead comes near it

* chore(studio): trim the scene hold helper's comments to its package's share

* fix(core): a scene the preview unmounts is freed, not kept by audio, caches or old roots

* fix(studio): an unmounted scene's rows and their state come from the markup it mounts with

* perf(core): the preview mounts scenes ahead of play and keeps recent ones mounted

* refactor(core): keep the far-scene neighbour rule private to its module

* style(core): format the far-scene neighbour rule

* perf(core): the preview unmounts scenes the playhead leaves, as kept ones slowed each mount

* fix(core): scenes that measure text, call their own animate or wait on font loads can unmount

* fix(core): each preview scene carries the helpers it loads, so it mounts on its own

* fix(core): a mounted scene shows though its stand-in was hidden by display

* fix(core): a helper scenes share runs once for the film, and a remounted scene keeps showing

* fix(core): a scene that also publishes its timeline under another id stays mounted

* feat(core): scenes that draw in 2D or chain the timeline they register can unmount

* perf(core): a play frame that draws nothing new skips the hidden-clip lookup

* fix(core): a GSAP file copied into a project counts as the library for scene unmounting

* style(core): drop an unneeded escape in the registered-chain pattern

* chore: core and producer size budgets make room for the preview scene window

* fix(core): a scene that draws on a 2D canvas stays mounted

* fix(core): a scene the preview unmounts lets go of timelines it built after a font wait

* perf(core): a scene leaving the playhead keeps its last rendered size, no forced layout at the cut

* refactor(core): the far-scene skip keeps last-render boxes in their own helper

* fix(core): a mounting scene never records the runtime's root timeline as its own

* chore(producer): raise the size budget to the measured size with the preview runtime inlined

* docs(core): keep the far-scene and scene-memory comments within the comment limits

* perf(core): style each hidden clip visible once ahead of its cut, so the cut frame does not

* fix(core): a seek into a scene still mounting as a neighbour waits for that mount

* chore(core): keep comment share and size budgets to main plus the measured core growth
2026-10-25 19:39:14 +00:00
Miguel Ángel c50918277a perf(engine): fast capture stays on for frames a shadow, blur or clip effect does not touch (#6318)
* perf(engine): fast capture stays on for frames a shadow, blur or clip effect does not touch

A single drop-shadow, blur, backdrop-filter or blend element, or one clip-path or mask
tween, sent the whole render to screenshot capture, 2-3x slower than drawElement. The two
init gates now name the frames the effect can show on. Only those frames capture by
screenshot, in a second browser opened on the first of them; every other frame keeps
drawElement.

* fix(engine): scan effects in a throwaway session; hand risk frames over only where captured

The effect scan seeked the render's own page to the end, leaving its timeline state ahead of
the render. It now runs in a throwaway session with the render's own seek. A hidden effect
element with a visible descendant counts as shown. Per-frame capture is opt-in per caller
(deFrameFallback), so disk, segmented, chunk and parallel-worker sessions keep the
whole-render fallback, and a route that cannot hand frames over drops drawElement up front.

* fix(engine): a timeline callback keeps the whole-render fallback; trim comment share

A tl.call or onComplete can add an effect's class to any element mid-render, so the elements
found at the start are not the only ones it can reach. Such a timeline is unbounded and keeps
the whole-render screenshot fallback.

* style(engine): shorten the live-frame scan comment

* perf(engine): after a timeline callback, not before, every frame may show the effect

A callback can add an effect's class to any element, so only the frames from the first
callback on lose their element bounds. Frames before it keep the scanned bounds, so a late
callback no longer sends the whole render to screenshot capture.

* perf(engine): open the second screenshot browser only when memory is available for it

Per-frame capture needs a second browser (479 MB measured at 1080p). When the render would
leave less than 1.5 GB available after it, the render keeps the whole-render screenshot
fallback instead. getAvailableMemoryMb moves from the CLI to the engine's system-memory
module, which already owns the host's memory readings; os.freemem() reads 367 MB on this
49 GB Mac, so the guard uses free, inactive, speculative and purgeable pages.

* fix(engine): budget the whole 1 GB per-frame capture adds, not just the second browser

The render's process tree goes from 1002 to 2001 MB at 1080p: the screenshot browser (479 MB)
plus a drawElement browser that is itself about 500 MB larger than a screenshot one. The guard
budgeted 500 MB. It now budgets the 1 GB measured, scaled by output pixels.
2026-10-25 19:15:18 +00:00
Miguel Ángel 69405a7736 perf(producer): size a chunk's capture browsers by its frame cost, not its frame count (#6319) 2026-10-25 19:14:30 +00:00
Miguel Ángel 1a37db4aee fix(producer): the audio peak correction sizes its cut from the PCM it re-encodes (#6312) 2026-10-25 18:53:14 +00:00
Miguel Ángel 42b84632a8 chore: release v0.8.199 (#6328) 2026-10-25 18:13:20 +00:00
Miguel Ángel 7f5d6ed203 feat(producer): fail a render that cannot finish within its maxRenderMs budget (#6224)
* feat(producer): fail a render that cannot finish within its maxRenderMs budget

* chore(producer): raise the producer size budget to the measured size of the render time guard
2026-10-25 17:31:18 +00:00
Miguel Ángel 26e3045e3b fix(producer): --workers N is a ceiling, drawElement renders keep their single worker (#6314)
* fix(producer): --workers N is a ceiling, drawElement renders keep their single worker

* docs(producer): keep the inversion predicate's doc as short as before
2026-10-25 15:33:05 +00:00
Miguel Ángel 7569ec1be5 fix(producer): extract HDR video frames a window at a time to bound scratch disk (#6304)
* fix(producer): extract HDR video frames a window at a time so scratch disk stays bounded

* fix(producer): drop the added compositor comment for the Comments gate

* fix(producer): keep the HDR scratch read plain and fail the render when a window cannot be extracted

* fix(producer): settle a looped HDR source's frame count before capture starts

* chore(producer): raise the packed size budget by the windowed HDR extraction's measured delta
2026-10-25 15:13:31 +00:00
Miguel Ángel d7f323d319 chore: release v0.8.198 (#6305) 2026-10-25 12:42:50 +00:00
Miguel Ángel aae32e43ed perf(producer): cut distributed chunks by estimated capture cost (#6295)
* perf(producer): cut distributed chunks by estimated capture cost, same chunk count

Auto-sized plans place chunk boundaries so each chunk carries about the same estimated capture
cost (page base plus active video megapixels); chunk count, explicit chunkSize, uniform-cost
compositions and the targetChunkFrames ceiling are unchanged. Frame-cost calibration samples by
cost interval plus the heaviest frame, so a heavy window is always measured.

* fix(producer): hold cost-weighted chunks to the worker time limit's frame cap

The weighted cut could grow a chunk up to MAX_CHUNK_SIZE frames, past the per-chunk frame count resolveMaxParallelChunks sized against the worker time limit. The cut now takes the same cap, from one shared helper, and keeps the equal-frame size as its floor.

* fix(producer): keep the chunk-cut doc comment under 120 characters

* perf(producer): drop the calibration sampler change from the cost-weighted cut

The cut no longer touches how capture cost is measured; that keeps the producer bundle within its size budget and the change to the chunk boundaries alone.

* perf(producer): compute the chunk frame cap inline to keep the bundle small
2026-10-25 11:25:00 +00:00
Miguel Ángel 683e9b1765 fix(producer): refuse private and non-HTTPS previewUrl fetches (#6297) 2026-10-25 09:50:18 +00:00
Miguel Ángel f51650d8a7 chore: release v0.8.197 (#6298) 2026-10-25 09:17:12 +00:00
Miguel Ángel 382a14d73e perf(engine): vp9 and vp8 sources without an alpha plane extract as jpeg (#6284)
A VP8/VP9 source with no alpha tag has one frame decoded with the alpha-aware decoder when its
metadata is read; if that frame has no alpha plane, its frames extract as jpeg instead of png.
Tagged alpha, a decoded alpha plane, a failed probe, or a plan written without the field keep png.
2026-10-25 08:32:49 +00:00
Miguel Ángel 45167743a6 chore: release v0.8.196 (#6288) 2026-10-25 06:52:05 +00:00
Miguel Ángel 38afc0b013 fix(deps): update esbuild for the security advisory (#6277) 2026-10-25 04:52:58 +00:00
Miguel Ángel 1776b42736 chore: release v0.8.195 (#6273) 2026-10-25 03:18:09 +00:00
Miguel Ángel 3eb1aeb0de perf(core): Safari stops re-serializing inline styles on every GSAP write (#5974)
* perf(core): Safari stops re-serializing inline styles on every GSAP write in a playing film

* perf(core): declare the clip-reveal subpath in its descriptor and cut added comment lines

* fix(shader-transitions): shader transitions across a scene boundary keep the scene's contents

Every revealer now goes through one core helper that shows the clip and
lifts the runtime's hidden mark together; the page-side compositor's scene
copies were still carrying the mark. The contents rule keeps its old weight.

* fix(core): films on an older shader-transitions build keep their transition scenes

Published shader builds reveal scenes by inline style without lifting the
hidden mark. When one is on the page, the runtime keeps the inline-style
rules; builds that lift the mark declare it on load and keep the fast rules.

* test(core): pin the hidden-clip contents rule's weight on its selector

* style(core): format the reveal test and move hidden-clip rule notes into names

* test(producer): pin hidden-clip behaviours in real Chrome under the mark-keyed rules

* test(producer): read the forced restyle's result so lint accepts the restyle test
2026-10-25 03:17:39 +00:00
James RussoandClaude Opus 5.5 7f19f2335a feat(studio): load GSAP from the CLI's bundled copy by default (#6256)
* feat(studio): load GSAP from the CLI's bundled copy by default

Studio preview now loads the GSAP, CustomEase and MotionPathPlugin scripts
it adds from files the CLI ships and serves same-origin at /api/vendor/npm,
instead of jsDelivr. HYPERFRAMES_GSAP_BASE_URL still wins when set, and a
composition on another GSAP version gets its plugin from jsDelivr.

Renders now honor HYPERFRAMES_GSAP_BASE_URL when they replace a missing
local GSAP script, and the setting accepts plain http only for loopback
hosts.

Closes #6205

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

* refactor(studio): keep comment share flat for the GSAP local root

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

* fix(cli): find the installed gsap with node resolution when run from source

The Windows CI lane installs with the hoisted linker, so gsap is not under
packages/core/node_modules there.

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

* fix(producer): load a nested composition's missing GSAP from the mirror

A sub-composition's missing local gsap/dist script was hoisted into the
render unchanged, so HYPERFRAMES_GSAP_BASE_URL (and the jsDelivr fallback)
only reached the entry document. Run the same rewrite on every
sub-composition before it is compiled, resolving its path the way the
inliner will hoist it.

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-10-25 03:08:42 +00:00
Miguel Ángel 2e397dba59 chore: release v0.8.194 (#6269) 2026-10-24 22:17:19 -04:00
Abdullah M. Nazif 0c3387b8be fix(producer): report unavailable frame checks in debug logs (#6200)
* fix(producer): report unavailable frame checks in debug logs

* chore: prepare conflict resolution against current main

* fix(producer): retain scoped diagnostics with current artifact handling
2026-10-25 00:28:25 +00:00
Akshar Patel 066bc51831 fix(producer): keep runtime media speed in exports (#5528)
* fix(producer): preserve runtime media playback rates

* fix(producer): preserve runtime-inserted speed ramps

* fix(producer): retain scene trims for discovered speed ramps
2026-10-25 00:25:04 +00:00
Abdullah M. Nazif d6c20513d1 fix(render): align container Chrome with the CLI (#5338)
* fix(render): align container Chrome with the CLI

* chore: prepare conflict resolution against current main

* fix(render): retain Chrome visuals and current audio baselines
2026-10-25 00:23:52 +00:00
James RussoandClaude Opus 5.5 f180ffca64 fix(cli): reap only orphaned browsers HyperFrames launched (#6251)
* fix(cli): reap only orphaned browsers HyperFrames launched

Orphan cleanup on preview and render killed every PPID-1 process of the
current user whose args named chrome-headless-shell or a Puppeteer temp
profile, which included detached browsers started by other tools.

Every HyperFrames Chrome launch now passes an inert
--hyperframes-owned-browser switch, and the reaper only kills PPID-1
processes that carry it as a whole argument.

Fixes #6203

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

* fix(core): serve the browser marker from its own subpath

The GCP image's BeginFrame contract step imports the engine's
browserManager straight from source before core's generated runtime
exists, so pulling the marker from the core root entry broke it.
A dependency-free @hyperframes/core/browser-marker subpath keeps that
import chain as light as it was.

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

* test(cli): expect the browser marker in the capture fallback launches

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

* docs(cli): say orphan cleanup runs before preview and render

The cleanup only stops browsers HyperFrames launched, so the opt-out is
no longer needed to protect other tools' browsers.

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-10-24 23:09:57 +00:00
Miguel Ángel 9aadc0b9b9 fix(producer): a busy composition no longer stalls the render waiting on probe-page reads (#6241)
* fix(producer): a busy composition no longer stalls the render waiting on probe-page reads

* fix(producer): shorten the probe read bound comment
2026-10-24 19:53:35 +00:00
Miguel Ángel 89081b9197 chore: release v0.8.193 (#6229) 2026-10-24 12:39:08 -04:00
Miguel Ángel da4386f0d0 perf(aws-lambda): cloud renders plan without reading video bytes and start chunks sooner (#6194)
* perf(engine): a remote source's tail decodes run many at a time, not one per half-core

* perf(producer): the plan logs where its extract stage spends its time

* perf(engine): a window that ends inside its source takes its frame count without a decode

* test(engine): pin the tail-decode slot test to the windows that decode

* style(engine): format the tail-probe window check

* test(engine): deferred counts match extraction on a B-frame NTSC source; VFR is not deferred

* style(engine): format the tail-probe tests

* perf(aws-lambda): a site stores each video's ffprobe JSON, and the plan reads it instead of probing

* test(aws-lambda): a site stores and hands out each video's probe

* style(aws-lambda): format the probe-facts upload and tests

* perf(engine): a site's stored probe also holds the final-frame timestamp the plan would probe for

* test(engine): drop an unused import

* test(aws-lambda): the stored probe holds the ffprobe JSON and the final frame

* test(aws-lambda): the stored probe holds the ffprobe JSON and the final frame

* perf(producer): a distributed chunk encodes while it captures

* perf(producer): the audio task logs where its mix spends its time

One line per audio stage: prepare, groups, mix, master, encode and the peak check, in ms.

* perf(engine): locked warmup ticks stop sleeping once the page is ready

* perf(engine): a window reaching a stored video's end takes its frame count from stored facts

* fix(producer): the cloud audio task logs its phases

* perf(engine): stored facts keep the last packet length so late-start windows skip the tail decode

* fix(engine): a probe spawns without waiting when no stored facts are declared

* chore(aws-lambda): shorten comments over the line limit

* fix(engine): warmup keeps pacing after a failed tick; stored final frames check ffprobe major

* test(engine): the recorded ffprobe major matches the build's own banner

* test(engine): the version-banner probes go through a named binary like the other spawns
2026-10-24 14:30:16 +00:00
Miguel Ángel 1ab871fb11 fix(producer): a render's page-error count includes what the capture workers saw (#6196)
* fix(producer): a render's page-error count includes what the capture workers saw

* fix(producer): a failed parallel capture still carries its workers' page errors

* fix(producer): every capture failure carries the page's errors, in every stage

* chore(engine): drop a comment the capture classifier's name already says
2026-10-24 10:33:03 +00:00
Miguel Ángel 0a8a78edec fix(engine): renames ride out Windows file locks instead of failing the render (#6210)
* fix(engine): renames ride out Windows file locks instead of failing the render

* fix(producer): the true-peak and segment-manifest renames ride out Windows file locks too
2026-10-24 09:51:28 +00:00
Miguel Ángel c16b783f5e chore: release v0.8.192 (#6206) 2026-10-24 08:51:29 +00:00
Miguel Ángel 381b654335 fix(engine): a clip with data-duration and no data-end ends where its duration says (#6195)
parseAudioElements now resolves end the way core's compileTimingAttrs does. A clip
with no length at all warns, naming the clip, instead of silently playing to the end of
its source.
2026-10-24 08:35:15 +00:00
Monster-GM 6c5ad56e46 fix(producer): forward GPU preference to HDR streaming encoder (#5030) 2026-10-24 08:15:42 +00:00
Akshar Patel c9ab2796af fix(engine): repeat looped audio through its export slot (#6184) 2026-10-24 07:18:32 +00:00
Miguel Ángel 53e1dfce9e fix(engine): h265 renders no longer fail on hosts with more than 16 cores (#6193)
* fix(engine): h265 renders no longer fail on hosts with more than 16 cores

libx265 rejects more than 16 frame threads, so the thread count taken from the
CPU quota is now capped at 16 for x265. x264 is unchanged.

* fix(engine): pass the encoder to the thread cap in the CFR re-encode
2026-10-24 05:33:56 +00:00
Miguel Ángel acb47de7d2 perf(producer): the audio task finds sound with a probe and reads URL clips in parallel (#6174)
* perf(producer): the audio task probes each video for sound instead of extracting its frames

* test(producer): the audio task finds a video's sound from a probe, with no frames extracted

* perf(engine): the audio mix reads clips held at a URL many at a time

* style(engine): format the remote clip slot choice

* fix(producer): the audio task places a video's sound in the window extraction would give it

* style(producer): format the slot cases of the script-src audio test
2026-10-24 04:23:15 +00:00
Abdullah M. Nazif ecf5db7070 fix(core): preserve blocked addresses in mounted import maps (#5277) 2026-10-24 04:11:18 +00:00
Abdullah M. Nazif bd4d8c6cb5 test: use supported FFmpeg flags for VFR fixtures (#5352) 2026-10-24 04:10:24 +00:00
Miguel Ángel da3e9b6385 perf(engine): a site's remote files are probed once per render, not once per clip (#6172)
* perf(engine): remote sources of a site keep their probe results for the whole render

* test(engine): probe a content-addressed URL once per render

* style(engine): format the audio probe split
2026-10-24 02:51:47 +00:00
Miguel Ángel cf7266d815 chore: release v0.8.191 (#6170) 2026-10-24 01:40:50 +00:00
Vance IngallsandClaude Sonnet 5.5 1c463a2fec fix(core): refuse nested vfx capture canvases and scope host lookups (#4804)
A `<canvas layoutsubtree class="hf-vfx-src">` nested inside another capture
canvas blocks the renderer main thread in drawElementImage, so `check` and
`snapshot` time out with no output. Refuse either direction of the nest with a
reportVfxError and leave the chain unregistered.

Also scope the two host lookups to immediate children (`:scope >`) so a nested
host's src/out canvas is never bound to the outer host, and read the
`data-vfx-ref-visible` flag in resolveCaptureSource so a canvas reached as both
a host's own source and as a ref gets one consistent `visible`/keepBitmap.

Refs #4405

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 01:30:58 +00:00
Dante 7dd83b8513 test: preserve browser cleanup and regression failure evidence (#5773) 2026-10-24 01:30:54 +00:00
Abdullah M. Nazif d25b18a007 fix(producer): close artifact frame validation gaps (#5242)
* fix(producer): close artifact frame validation gaps

* docs(producer): describe quiet artifact metadata validation
2026-10-24 00:43:17 +00:00
Abdullah M. Nazif aac0e7df51 fix(producer): make sparse keyframe advice conditional (#5343)
* fix(producer): make sparse keyframe advice conditional

* fix(producer): clarify sparse keyframes do not affect rendering
2026-10-24 00:43:14 +00:00
Miguel Ángel 3439433d67 perf(aws-lambda): a render reads a video's audio from a small file stored beside it (#6162)
* perf(aws-lambda): a render reads a video's audio from a small file stored beside it

A video's audio is interleaved with its pictures, so mixing it from the cloud read the whole video (2.5 GB for a 20-minute 4K film) at the Lambda's network speed. lambda sites create now stores each large video's audio packets as a separate object keyed by the video's hash; the audio task reads that when it exists.

* chore(producer): shorten the audio sidecar header comment

* fix(aws-lambda): no audio sidecar for a video whose audio starts late or that has several tracks

* test(producer): format the audio sidecar tests and end their ffprobe options with --
2026-10-23 23:12:52 +00:00
Miguel Ángel e48d5d4a5c chore: release v0.8.190 (#6158) 2026-10-23 21:54:34 +00:00
Miguel Ángel 73718a251b fix(engine): the BeginFrame start-up check waits for the first frame up to its deadline (#6149)
* fix(producer): a chunk with mixed capture modes retries whole in screenshot mode

* fix(producer): format the mixed capture mode change

* fix(engine): the BeginFrame start-up check waits for the first frame up to its deadline

* style(engine): format the probe tests

* fix(engine): drop a comment from the probe loop
2026-10-23 20:52:14 +00:00
Miguel Ángel f8ae1483c5 fix(engine): the video frame cache budget is shared by the capture workers (#6135)
* fix(engine): the video frame cache budget is shared by the capture workers

Each capture worker built its own frame cache of up to 1500 MB of base64 strings, all on the one Node heap, then serialised every frame body for the browser. With many workers and 4K png frames the heap ran out inside JSON.stringify and the render aborted. The configured budget is now split evenly across the workers, never under 64 MB each.

* chore(engine): format and trim the frame cache budget change

* chore(engine): format the frame cache budget test

* fix(producer): chunk capture workers share the frame cache budget too

The distributed chunk path built its injectors with no options, so each worker kept the full 1500 MB cache whatever the config said. The factory now takes the budget and renderChunk passes it with the chunk worker count.

* test(producer): run the chunk injector budget test under bun and format

* fix(producer): divide the frame cache budget across workers in the producer, not the engine

The engine package was 13 bytes over its packed-size budget with the worker count threaded through it. The orchestrator and the chunk path now hand the engine each worker's share of the budget, one helper owns the division, and the engine is unchanged.
2026-10-23 19:30:53 +00:00
Miguel Ángel b7e2278e24 chore: release v0.8.189 (#6140) 2026-10-23 18:13:30 +00:00
Miguel Ángel 132a504b69 fix(engine): encode with the same x264 and x265 thread count on any host size (#6127)
* fix(engine): encode with the same x264 and x265 thread count on any host size

libx264 and libx265 pick their thread count from the host core count when
ffmpeg is not told one, and the bitstream depends on it, so the same frames
encoded to different bytes on 8, 16 and 32 vCPU nodes and chunks of one
render could differ. Both encoders now pass the thread count from the CPU
quota (Node reads the container limit), which is fixed per pod.

* chore(engine): raise the packed size budget for the encoder thread module
2026-10-23 14:13:57 +00:00