The 'loop' temporal style (in the style list since forever, but inert) now does
what WanVideoWrapper's Loop Args does for WAN: on eligible denoise steps the
packed latent is cyclically rolled along its temporal dim before the forward and
the prediction unrolled after, so the model repeatedly sees the video's seam
(last|first frame) as an interior frame pair at step-varying positions (van der
Corput sequence, reset per scene) and smooths it into a natural loop. Video and
audio streams roll by their own frame counts and stay time-aligned; denoise
masks roll in step, so i2v anchor per-token timesteps stay consistent.
Near-zero overhead: two clones + torch.roll per eligible step, no extra forward
passes. Rolls only below the near-noise plateau (nothing to smooth up there, and
the plateau step-cache stays valid). Calls carrying guides are left canonical —
keyframe_idxs pins appended guide frames to absolute positions inside the same
T range, so rolling them would corrupt; on guide-carrying scenes the roll is
inert and the run mechanism log says so.
Wiring: concrete style via the global build_enhancements path (innermost
relative to per-scene wrappers, as required — prediction-modifying wrappers see
canonical orientation only); auto director's funpack_temporal_loop via a tagged
per-scene wrapper on the Scene Chain sampler, installed below the guidance
stack. Editor already exposes 'loop' in the temporal style dropdown.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/result: a failed faststart remux now answers 502 (rate-limited re-attempts)
instead of permanently marking the file serve-raw — raw moov-at-end bytes are
undecodable on seek in every browser (Firefox: NS_ERROR_DOM_MEDIA_DECODE_ERR /
AppleVTDecoder) and the element's cache-busted retries then fail identically
forever. Temp-dir failures 503 instead of falling back to raw; the loopback
fetch_view fallback spools ISO-BMFF video to temp and serves it through the
same remux + FileResponse path (Range support included); moov gates now cover
.m4v/.mov too.
player: materialize <video> elements only in a window around the playhead
(2 behind / 8 ahead, 16 max) instead of one per timeline clip. A montage-scale
timeline (100 clips) created 100 elements and 178 concurrent preview-segment
requests at once: Chrome hard-blocks media players past 75 per page, and the
per-origin connection pool starves on tunneled instances — clips in the back
half of the timeline never loaded while the front half played. Evicted and
reset elements release their src before detach (connection-pool class).
timeline: filmstrip probes run through a shared 2-at-a-time queue with a
20s wedge guard — painting a big timeline no longer starts one preload=auto
element per clip simultaneously.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sample_funpack_distilled_flow built the alg_latents dict eagerly, and the
(True, True) value used 'anchor_blurred or tail_blurred' as its fallback when
both_blurred was None. When anchor blur is on but tail blur is off (or the tail
blur returned None), anchor_blurred is a multi-element tensor, so 'or' calls
bool() on it -> 'Boolean value of Tensor with more than one value is ambiguous'.
Surfaces only on continuation scenes: an anchorless first scene (identity/empty
latent, no denoise_mask) skips ALG entirely, but a later scene with a guide mask
enters ALG and trips the eager dict build. Replaced the tensor 'or' with explicit
None chaining (both -> anchor -> tail). Pure fallback; when (True,True) is actually
indexed both blurs are on, so both_blurred is non-None and the singles are never
reached. Independent of taste_nearest_prompt, which was merely on in the same run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds taste_nearest_prompt on the Scene Chain sampler. On every liked rating the
Refiner now records a (pooled-conditioning fingerprint -> liked direction) entry in a
ring buffer beside the global liked_dir. When the toggle is on, each scene retrieves
the similarity-weighted direction of its nearest rated prompts and uses that for
embed_guidance / score_slider instead of the one global average, falling back to the
global direction when nothing is close enough. Non-parametric (cosine lookup + vector
mean, no extra model forward, no value-function collapse risk). Wired into the Editor
Guidance panel. UNVALIDATED LIVE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MixCache/Chorus-family step-caching adapted to LTX2.3's distilled schedule.
On the near-pure-noise plateau (sigma >= threshold, default 0.975) the
transformer output barely changes, so compute the base-model forward once
and reuse it for the remaining plateau steps, skipping ~3-4 of 8 full
48-block forwards. Below threshold every step computes for real and the
cache drops, so structure formation is never approximated. Deterministic
given seed -> safe in Batch Training, no rating-diversity impact.
Installed innermost as a per-scene model_function_wrapper (existing
tag/strip leak hygiene) so guidance wrappers still post-process each step's
cached-or-fresh prediction. Cache keyed by (input.shape, cond_or_uncond).
Editor: plateau_cache + plateau_cache_threshold in Engine -> Experimental,
auto-wired via the sampler_inputs bridge (no backend/builder change).
Off by default, UNVALIDATED LIVE. (Chorus cross-variant reuse rejected --
would blur batch-rating diversity; FreeSpec dropped -- wrong topology.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wiring one node's output into another node's WIDGET input (e.g. Image
Transform's width/height → Empty Latent's width/height) works at generation —
the builder replaces the widget value with the connection — but the widget
field on the destination's node page stayed fully editable with no hint that
its value is ignored while wired.
Now a widget input with an incoming wire renders like a linked control: field
disabled, '⇐ <SourceNode> · <output>' tag (with an explanatory tooltip), eye
button hidden since an exposed control would edit a value generation ignores.
An ALREADY-exposed such widget in the main editor's Exposed controls block
renders the same wire-source note instead of an editable field (mirrors the
'← Project FPS' editor-link pattern). Unwiring restores the editable field and
eye button — the lock is computed from the wires on every render.
Verified live: GetImageSize width/height wired into EmptyLatentImage's
width/height widgets — both fields disabled with '⇐ GetImageSize · width/
height' tags on the node page, batch_size untouched, and the exposed width
control in the inspector shows the wire note instead of a number input.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reported as 'exposed Bypass toggle says disabled but the node's own checkbox
is checked (i2v still bypassed)'. Root cause in Store.commit(): the project
snapshot was deep-copied BEFORE _syncEditorStateToProject() ran, and only
scene_renders/scene_ghosts were backfilled afterwards — snapshot.models kept
the models config from the PREVIOUS sync. So after any exposed-control edit
(bypass, node input, or link — all saved via the /models endpoint), the next
project autosave uploaded the stale models copy inside the project body and
overwrote p.models on the server. The exposed control kept showing the local
value, the Models window fetched the server truth, and generation used the
server value — the exposed edit never took effect.
Fix: sync editor state into the project BEFORE taking the snapshot (the
backfill lines become redundant and are gone). Verified live against local
ComfyUI both ways: uncheck the exposed bypass → scene edit → autosave →
server models stay bypassed:false; toggle back on via the Models window's
save+event path → another autosave → server stays bypassed:true. 160
movie_editor tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A clip whose render isn't decodable yet used to look broken — a black or
blinking <video> with console decode errors — with no signal about WHY or
whether it would recover. Now the player tracks per-element readiness
(loading → ready on loadedmetadata; downgraded on media reset) and the
full-cover slate says what's happening whenever the active clip's element
isn't ready:
- 'Video is processing…' — the server answered 503 on a HEAD probe (render
still being written to disk, round-4 gate). These retry on a steady 3s
cadence with the retry budget refunded, so a long GIL-bound save can no
longer exhaust the 4 retries and leave the clip dead.
- 'Video is loading…' — bytes on the way / cache-busted retry in flight.
- 'Preview unavailable' — retry budget exhausted (re-armed on the next
timeline rebuild, as before).
HEAD /result now mirrors the GET path's no-moov 503 so the probe can tell
'still being written' apart from a dead file (it used to claim 200 for a
file GET would refuse). Playback was already gated on loadedmetadata via
_playPending, so the preview only goes live once the video really decodes —
the slate makes that state visible instead of black.
Verified live against local ComfyUI with a real truncated mid-write mp4:
slate shows 'Video is processing…' while the file has no moov, HEAD answers
503, and within one 3s retry of the file completing on disk the slate clears
and the clip decodes and plays (readyState 4, zero media errors in console).
160 movie_editor tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three holes left after the /result faststart remux (e296fe0), matching the
still-live symptoms (decode errors in console, blinking clips, playback
restarting from 0, stale previews):
1. Mid-write serving: /result hit while VHS is still writing the mp4 found no
moov, failed the remux, and served the raw moov-less bytes anyway —
undecodable, and the player's cache-busted retries kept blinking against it.
2. Byte-switching under one URL: once the write finished, the same URL switched
from raw to remuxed bytes with different offsets/length. A browser mid-Range
stream mixes offsets (PIPELINE_ERROR_DECODE) or fails If-Range revalidation
and falls back to a full 200 — resetting the element and restarting the clip
from the beginning.
3. Cache collisions/staleness: the remux cache was keyed by path only (a
rewritten source kept serving the old remux) and the remux output was named
by basename only (two sources sharing a basename overwrote each other's
remuxed copy with -y, possibly while it was being streamed).
Fix: _is_faststart becomes _moov_position (front/end/none) so "remux me"
(moov at end = complete VHS file) is distinguished from "come back later"
(no moov = still being written or aborted save). No-moov sources now get 503 +
Retry-After from both /result and preview-segment instead of undecodable bytes;
the player retry loop already handles that. The remux cache is keyed on the
source's (mtime_ns, size) signature, the output name is derived from
path+signature (no basename collisions), the remuxed file is validated
(nonzero, moov at front) before caching, and a stable-but-unremuxable file is
remembered so ffmpeg isn't re-run per request. The preview-segment cache key
also gains the source signature so a re-rendered source at the same path can't
serve segments encoded from the old bytes.
Verified against real ffmpeg files: end/front/none discrimination on complete,
faststart, truncated-mid-write, and empty mp4s; remux of a truncated file
raises instead of caching garbage. All 160 movie_editor tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ComfyUI's VHS_VideoCombine node writes moov at the end of the mp4 (no
+faststart). Deep-seeking that file is exactly what preview-segment already
routes multi-scene chain clips around, but a "shallow" single clip (inSec
<=0.5) or just opening a scene's own preview hits /result directly, gets
served the raw moov-at-end file, and fails to decode the moment the user
scrubs anywhere into it — reproduced live as repeated
NS_ERROR_DOM_MEDIA_DECODE_ERR/AppleVTDecoder failures on the same file even
after cache-busted retries, since the bytes on disk are permanently bad for
seeking, not transiently mid-write.
Fix at the source instead of extending the segment path: /result now
stream-copy remuxes (+faststart, no re-encode) any .mp4 it serves that
doesn't already have moov up front, caching the remuxed copy so it only
happens once per file. FunPack's own segment/export/final-render output
already sets +faststart, so this is a no-op for everything except the raw
VHS output — verified the atom-order detection and remux against real
ffmpeg-produced faststart/non-faststart files.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
model=object() lacked model_options (needed by the per-scene wrapper
snapshot/restore added for the scene-wrapper-leak fix), and FakeVAE lacked
decode() (needed since the sampler always decodes IMAGES when the caller
doesn't declare that output as disconnected) — together these silently
killed every test in the file that calls sample(). Added FakeModel and
FakeVAE.decode, then fixed the resulting return-tuple arity (sample() now
returns 6 values, these tests unpacked 4) and three genuinely stale
assertions: the per-scene report line gained a timing segment, carry_i2v_guides
now prepends its guide frame instead of appending it, and _append_i2v_guides
no longer routes through keyframe_idxs/guide_attention_entries.
Verified in isolation: all 14 tests in this file now pass. Running the full
tests/ suite still shows the same file failing — that's a separate,
pre-existing cross-file sys.modules stubbing collision (confirmed via
git stash on a clean dev checkout) affecting this file plus
test_mask_safe_attention.py and test_v2_category_alignment.py; not fixed
here since it isn't a one-line root cause.
carry_overlap_through_anchor lets a scene keep the previous scene's latent
tail for background/environment continuity even when it switches to its own
i2v anchor, instead of the current hard cut. The anchor's own leading frame
stays untouched; only the frames after it carry the old tail.
Also fixes a real gap found along the way: the mixed-anchor branch never
called _apply_configured_guides, so Best-FaceID identity_transfer could
never resolve an identity_pin on an anchor-swap scene — exactly where it's
most useful. Added a standalone lookup so it resolves there too.
Today's preview-stability fix (4bc56e8) made the player pool fetch every
chain-shared clip through a trimmed, faststart preview segment instead of
deep-seeking the raw chain output (moov-at-end, stalls/blacks in the
browser). Two other consumers of the raw file were missed:
- appendFilmstrip built a <video preload=auto> pointed at the full shared
chain output and seeked into it per thumbnail — the same deep-seek
hazard the player fix eliminated, just in a different element.
- sceneAudioClip's waveform did a plain fetch()+arrayBuffer() of the
ENTIRE shared file, once per scene sharing it, to decode a waveform.
Repro (fake 3-scene chain sharing one moov-at-end mp4): opening the
project fired 15 aborted requests fighting the player pool over Chrome's
6-per-origin cap — exactly the stale/black/reset-to-start symptoms
reported live. Both now request the same cached preview-segment URL the
player uses; post-fix, scrubbing 11/11 test positions succeeded with no
black frames or resets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the source-phase-RoPE-only approximation with a faithful native port of
ComfyUI-BFSNodes' LTX Identity Transfer (read upstream source directly): the reference
face is now injected as separate, non-rendered overlap tokens (clean timestep, RoPE
source-phase tag, trimmed before output) instead of blending into Continuity's identity-pin
keyframe, plus an optional ArcFace embedding -> IdentityProjector -> context-token channel
(identity_projector, source_id, phase_scale, id_strength, arcface_mode, debug_log). Stays
on Chain Sampler per the Studio/Sampler boundary law's guide-ops carve-out. New
identity_transfer.py module; insightface added as a soft pip dependency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Models & Pipeline -> +New node: picking a node now collapses the search results
into a one-line "selected" row with a Change button, instead of leaving the full
list open above the Values/Wiring panel.
Prompt autocomplete: dropped the MAX_ITEMS=8 cap on matchTriggers — a prefix
shared by many shortcuts (e.g. 15 "lazy" triggers) now shows all of them; the
menu already scrolls (max-height in CSS), so nothing was gained by truncating.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverts the earlier ComfyUI-BFSNodes graph-splice approach (builder.py/timeline.py/
server.py scaffolding) in favor of porting the missing piece natively: FunPack's own
guide-attention path (LTXVAddGuide.add_keyframe_index + guide_attention_entries)
already places a reference image on the frame-0 grid exactly like BFSNodes' overlap
technique, and Continuity's existing "Identity pin" already applies one reference
face to every scene. The only real gap was the LoRA's trained-time source_phase RoPE
tag, added as a tag+strip patch on FunPackLTXAVSceneChainSampler scoped to the exact
guide-entry token range (never co-active mid_scene/carry/prior-scene guides sharing
the same keyframe_idxs tensor). New "Best-FaceID compatibility" + "Source-phase id"
knobs live under Engine Settings -> Experimental, alongside every other unvalidated
technique. LoRA loading itself reuses the existing generic Models-menu lora role.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Detects ComfyUI-BFSNodes' LTX Identity Transfer node in object_info and, when
enabled, auto-loads the Best-FaceID LoRA onto the model path and splices the
identity node between Studio and the Chain Sampler (project-wide, one
reference face per project — a run's graph can't vary it mid-chain). New
Engine settings pane under Chain Sampler picks the reference face (media
picker) and LoRA file (live dropdown), with advanced ArcFace/phase knobs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Four compounding defects behind stale/black previews on live instances:
1. /preview-segment ran its ffmpeg x264 re-encode synchronously inside the async
handler — each encode froze ComfyUI's single aiohttp event loop for seconds,
stalling every streaming /result response mid-transfer (elements error out,
retries cache-bust and refetch, transport wedges). Worst right after a run,
when the pool requests every clip's segment at once. Now asyncio.to_thread
with a per-key lock (scrub storms don't spawn duplicate encodes) and
-preset veryfast for preview trims. /export-clip wrapped the same way.
2. Ghosts were excluded from the segment system, so scrubbing a removed scene
deep-seeked the raw moov-at-end chain MP4 — the exact stall segments were
built to avoid. Ghosts (and lone clips with a deep in-point, e.g. the last
surviving scene of a chain) now get segments; the trim window travels in the
query (render_in + dur) since the scene no longer exists server-side.
3. Real staleness: segment URLs lacked the clip duration while responses are
browser-cached for an hour — trimming a clip kept the same URL and replayed
the hour-old segment. dur is now part of every segment URL.
4. Every pooled <video> used preload=auto: the whole timeline downloaded
simultaneously, starving the browser's per-origin connections on tunneled
instances and aborting the stream actually being watched. Pool now idles at
preload=metadata; the current clip and its neighbours are promoted to auto.
Verified live on local ComfyUI with a synthetic 2-scene chain + ghost: ghost
segment 200/video-mp4, API stays responsive during an encode (17ms ping),
scrub across boundaries, seek into ghost (segment-relative time + badge),
play across the scene→ghost boundary, rapid pause/resume — all clean.
160 editor tests pass (new: ghost clip-spec parsing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alg_blur_guides no longer borrows the sampler's anchor knobs: the Scene Chain
Sampler gains alg_guide_blur_strength (default 2.0) and
alg_guide_blur_sigma_threshold (default 0.975), threaded per-chunk via
extra_options like the tail count. The sampler now precomputes one latent per
(anchor blurred?, tail blurred?) combination — the frame sets are disjoint, so
the both-blurred variant composes the two passes — and each region follows its
own sigma window per step. Anchor ALG (alg_enabled/alg_strength/
alg_sigma_threshold) is untouched and back to anchor-only semantics.
Editor: 'Guide blur strength' and 'Guide blur sigma threshold' rows appear
under Engine ▸ Experimental when the blur toggle is on (sampler_inputs bridge,
names match the new node widgets; inputs appended at the block end to keep the
builder's positional widget mapping aligned).
Verified live: object_info shows both widgets appended last; Editor rows
appear on toggle and persist to project.sampler_inputs. 11 ALG unit tests
pass (new: composed independent-strength blur); same 20 pre-existing suite
failures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Editor ▸ Settings ▸ Editor gains a 'Shortcut revolver' toggle (+ 'Random' sub-option).
When on, each shortcut with several replacements draws from a per-shortcut queue —
sequential (first…last, then restart) or shuffled once per cycle — and nothing repeats
until the whole set has fired. Off (default) keeps the seeded random pick unchanged.
- State is a sidecar (shortcut_revolver.json) next to the shortcut DB: settings +
per-shortcut remaining queue + replacement-set fingerprint (editing a shortcut's
replacements restarts its cycle; changing either setting resets all cycles).
- Generation commits the draw (refine_v2 positive prompt); every preview path
(parse_timeline, split mapping, fragments) peeks the same stored queue without
saving, so previews show exactly what the next Generate will use.
- Batch Training variants each commit a draw: N variants sweep N consecutive
chambers instead of seed-rolled repeats.
- FunPackStudio.IS_CHANGED returns nan while the revolver is on so fixed-seed
re-runs aren't served from ComfyUI's node cache (the next chamber must fire).
Verified live on local ComfyUI: settings round-trip, seed-independent stable peeks,
sequential cycle REP_ONE→TWO→THREE→ONE across processes, peek-after-commit shows the
next chamber. 9 new unit tests; full suite has the same 20 pre-existing failures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alg_blur_guides now works with the sampler's alg_enabled off: the anchor
stays sharp and only the newly-appended guide-attention frames (mid-scene
guide, carried guides, per-scene guides, JoyAI memory) are blurred, reusing
the same alg_strength / alg_sigma_threshold parameters and sigma window.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bulb's popover now mixes what the editor knows about YOUR prompting habits
with library browsing, in four sections:
- You've paired these before — shortcuts you historically put in the same shot
as what's in the prompt, ranked by co-occurrence (reason chip: with «X» ×N)
- Your usual next shot — what usually follows the previous scene's shortcuts
in timeline order (scene inspector only; reason chip: after «X» ×N)
- Continue the thread — more from categories the prompt already uses
- New directions — the existing missing-category browser (counts, +N more)
Habits are mined on demand by GET /api/library/suggestion_stats: a text scan of
all saved projects' scene texts (triggers stay verbatim in scene texts, so no
new persistent state — project files remain the only source of truth). Pairs =
same scene, follows = consecutive scenes in timeline order; editorial cut
subclips and excluded scenes are skipped. One-off co-occurrences (<2) ignored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📌 hover action on image cards sets/unsets continuity_settings.identity_pin_ref
(same setting as Engine settings ▸ Continuity ▸ Identity pin, just quicker).
The pinned card shows a persistent 📌 badge on its thumbnail. Pinning while
Auto continuity is explicitly off re-enables it — a pin that does nothing
would be a hidden setting. Image cards only, chain-sampler pipelines only.
fpMediabrowser now fingerprints identity_pin_ref so pin changes repaint the
gallery zone (they were dropped by the ViewBus fingerprint before).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A 💡 next to the scene prompt and Composer global prompt lights up when whole
shortcut categories (camera moves, sub-actions, …) exist in the library but are
absent from the prompt. Click-only (never auto-opens): a category-grouped
popover lists every missing category with count, random insertable samples,
+N-more browse per category, and a shuffle. Chips insert at the caret through
the field's normal input path. Toggleable via Settings > Editor > Shortcut ideas.
Guards: no popover off a hidden/detached bulb (stranded top-left window),
dismiss listeners detach on every close path, popover closes when a re-render
removes the bulb.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fast pause/play over the network can abort the stream fetch and reset the
<video> element (the retry path's cache-busted load() does the same): the
element rewinds to 0, the tick stomped the playhead to the clip start, and the
next play restarted from the beginning. Now 'emptied' stashes the playhead as a
pending seek, loadedmetadata restores it (and resumes when playing), and the
tick treats readyState<1 as seeking so a resetting element can't feed a bogus
currentTime. Reproduced+verified via forced mid-playback load(): position holds
while playing, mid-burst, and while paused.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The main Settings sidebar collapses to a 52px icon rail and expands to full
width on hover/focus, overlaying the content (no reflow). Node values render in
one column instead of two, Engine's Conditioning/Sampler pickers stack, and the
wiring/fields shrink chain got min-width:0 everywhere a long select option used
to force horizontal overflow — the node page header actions (bypass/remove) now
sit in a wrapping cluster and stay reachable at any width (live-instance bug:
remove button was unreachable, no horizontal scroll).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
splitScene set frames on both halves but left frames_mode='project', which
sceneEffFrames (and backend eff_frames) IGNORE - so the first half kept full
project length (looked untouched) and the second half rendered as a whole
extra scene. Both halves now flip to 'timeline' mode, same as resizeScene.
Also: with a pinned custom cut order, _ensureTimelineOrder appended the new
half's id at the END of timeline_order - the 'split adds a scene to the end'
symptom. The second half is now spliced into the cut order right after its
first half.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The FunPack menu and the Settings dropdown are gone; the top bar is
File / Edit / View / Help / Settings, where Settings is a plain button that
opens the unified window (keeps .menu-btn[data-menu=Settings] for the tour).
Everything they held already lives in Settings sections, plus the two
stragglers migrated: Reset Studio session (Refinement & Taste, keeps the
armed/cancel two-step + confirm and Store-driven label) and Import ComfyUI
Workflow (Models & Pipeline header action next to Refresh). Tour/FAQ texts
updated to the new paths. FunPackMaintenance/FunPackGit stay the single
implementations behind the Settings rows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One Settings window replaces the four scattered modals: searchable sidebar with
colored icon chips grouped as Editor / Generation (Engine, Models & Pipeline) /
Learning (Refinement & Taste) / System (Updates & ComfyUI, Temp Files). Sections
self-register on a small shell (settings_window.js); the legacy modal facades
(EditorSettingsModal/EngineSettingsModal/ModelsModal/TempBrowserModal) deep-link
into their section so every existing entry point still works. Refinement-key and
restart/update actions get proper panels reusing the single menubar
implementations (window.FunPackMaintenance). Cmd+, opens it; tour's overlay
sweep now closes the window through its cleanup hooks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'Awful' / 'Wrong appearance' banked on intrusion semantics; now any rating
whose profile reward marks the run a bad OUTCOME (<= -0.25: Missing
quality, Missing details+quality, Missing action+quality, Missing
details+action+quality) also feeds the negative bank - degradation is
visible in the latent, so steering off that attractor is meaningful.
Near-miss ratings with positive reward (Missing details +0.35, Missing
action +0.05) stay excluded: those latents are mostly-wanted content and
banking them would repel future runs from material the user asked for.
Decision moves into negative_memory.is_negative_profile (intrusion keys OR
reward threshold, robust to future profiles); promoted entries now carry
the rating key as provenance for severity-weighted steering later.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Store what went WRONG, not just what was asked for. Ratings 'Awful' and
'Wrong appearance' promote that run's final video latent (fp16, batch
squeezed) + mean-pooled conditioning into a per-key negative bank
(<key>.negative_latents.pt, ring buffer of 8). The Chain Sampler writes a
pending candidate at the end of every keyed run; the rating that scores
that run promotes or discards it - same pairing as the x0 snapshot, and
the stem glob in delete_refinement_key covers both new sidecars.
Sampling (dynashift toggle + strength/threshold, off by default):
per late step each current video frame is pooled and compared against
every banked negative frame (alignment-free in time, so chain position /
guide tails don't matter; other resolutions are skipped). Frames matching
above threshold get the matched negative frame's aligned component
subtracted (projection removal, coef clamped >= 0), scaled by
above-threshold margin - steering self-releases once the unwanted feature
is gone. Negatives are weighted by prompt similarity so bad gens from
unrelated prompts steer less. No uncond pass, no extra model forward;
audio byte-identical (video span slice + cat). Wrapper rides the tagged
per-scene lifecycle (leak-strip covered) and threads through batch
training.
Editor: DynaShift knobs land in Engine Settings > Guidance same-change
(sampler_inputs pass-through, no backend change), verified rendering live.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two audit findings on the experimental feature stack:
1. Leak: an interrupt or OOM inside _sample_chunk skipped the per-feature
unwind blocks, leaking embed_guidance/score_slider/output_guidance/temporal
wrappers on the shared ModelPatcher - every later run then chained new
wrappers onto the leaked ones and double-steered (same in-process
progressive-corruption mode as the old block-hook leak).
- one base-wrapper snapshot per scene, restored in the same finally that
removes the v2a hooks; the four conditional unwind blocks are gone
- every per-scene wrapper is tagged with its predecessor; sample() strips
leaked tagged chains at run start (mirrors strip_funpack_block_hooks)
2. Inert feature: output_guidance applied the RAW value-fn gradient, whose
per-element magnitude is O(1/pool_window) - at video-latent sizes the
correction was ~1e-6 relative, i.e. numerically a no-op. It also fed the
full packed AV tensor to a value function trained on the video stream
alone (shifted pooling buckets + audio contamination).
- gradient now computed on the packed latent's video span only (matches
the training domain of _save_output_value_snapshot)
- delta norm-calibrated: strength = fraction of video-stream norm per
fully-ramped step (0.02 = 2%), same relative convention as the slider
- audio region byte-identical by construction (cat, not post-hoc mask)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The per-scene line now includes run_mechanisms, so a slow guide scene
attributes itself fully: which feature appended the tokens
(custom_guide_stack / carry_i2v_guides / joyai_memory / mid_scene_guide
/ soft_continuation) alongside the tail counts and sampling seconds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live report: guide scenes suddenly ~3.5x slower than the anchor scene
(6-scene 768x768x305 montage at 7 min vs ~2.5 expected), on a nightly
ComfyUI. Audit of the upstream delta found nothing touching the default
guide attention path (context windows is opt-in, int8 irrelevant, the
guide-strength mask machinery dates to February) - so before theorizing
further, make each run attribute itself: every scene line now carries
its sampling seconds plus what was appended to its sequence (carried
head frames, guide tail, audio tail), printed live and in the report.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Block steering: run-averaged block activity is architecture-determined
(varies in the 4th decimal run-to-run), so no scoring scheme - pole EMAs
or reward correlation - could extract a rating signal from it. The idea
needs within-run per-step measurement to have a chance; that is a
research project, not a feature. Removed: module, sampler wiring, the
two trailing widgets (positional reference mapping unaffected), the
Refiner pairing hook, editor knobs, tests. Stale block_snapshot/
block_profile sidecars are inert and covered by the existing
delete_refinement_key sweep.
FunPackNVFP4ModelLoader: the FP4 GEMM path was real (3.35x at 16k
tokens) but end-to-end the user's runs are dominated by everything the
loader cannot touch (attention, prep, encode - the sampler node is
~23s of a ~60s run) while dynamic input quantization added a fixed
per-layer-call toll. Net observed: overhead. Users are better served
by dedicated NVFP4 loaders. Removed: module, registration, tests.
Kept (independently useful): the chain sampler's phase timing report +
quantized-layer census, the nested-AV x0 snapshot fix, and the sm_120
masked-attention work.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Justified by live data: sampler phase timing shows sampling at 89% of
the chain node, all 792 quantized layers on the fast path, and the probe
puts dynamic input quantization at ~0.30 ms/layer-call vs ~0.11 ms with
a provided scale (amax dominates) - a top-line per-call cost.
New input_scales widget on the loader:
- "auto-calibrate", first load without a sidecar: forward-pre-hooks
record each quantized layer's running input amax (one generation is
enough; sidecar under refinements/nvfp4_scales/<checkpoint>.json,
per-machine and rebuilt in one run whenever absent).
- every later load: injects input_scale = amax * 1.25 / (448*6) into
the state dict next to each quantized weight - core's loader
registers it natively and skips the per-call amax from then on.
Margin leaves clip headroom (NVFP4 clips above the scale).
Tests: suffix-matched injection across checkpoint prefixes, value math,
core Linear registering input_scale and forwarding, sidecar read.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live report: "flat activity across runs" with a working profile. Root
cause is real, not a threshold tuning issue: the fingerprint is each
block's MEAN residual activity over ~1200 steps, which is practically a
property of the architecture - run-to-run variation sits in the 4th
decimal, so raw reward-covariance is numerically dead. Lowering the
epsilon would have "fixed" it by steering on noise.
Now: per-block Pearson CORRELATION between reward and activity (scale-
free - tiny variations count when they consistently track ratings),
fingerprints stored mean-normalized so run-global scale (step counts,
schedulers) can't masquerade as block signal, and a confidence ramp
(min(1,(runs-2)/6)) so a 3-run profile steers gently instead of not at
all, reaching full strength ~8 runs. MIN_RATED_RUNS 2->3 (correlation
over 2 runs is degenerate). Status lines now carry the evidence:
"ready (N runs, peak |corr| X, confidence Y)". Older profiles are
discarded on first pairing (fingerprint semantics changed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"The matmuls got faster" and "the video arrives sooner" are different
claims. The run report and log now show total = sampling + VAE decode +
other with percentages, so an optimization's real ceiling is visible
(at 768x768x305 the ~22k-token attention rivals all linears combined,
and decode/prep are NVFP4-blind - a 2x on quantized GEMMs can cap out
around seconds of a one-minute run).
Also logs a quantized-layer census at run start: layers forced OFF the
quantized matmul path by weight patches (LoRA / refiner attn deltas) or
emulation dequantize on EVERY call - silently slower than bf16.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live numbers from the rental (sm_120, cu130): FP4 GEMM wins 3.35x at
M=16384 (1.97x net) but loses at M=1024 to launch overhead, and input
quantization is a near-fixed ~0.2-0.3 ms per layer-call. With scope
'all blocks' (1232 layers incl. the small audio branch) that fixed toll
cancels the GEMM savings - the exact "speed didn't change" report.
The probe now: times input quantization with a precomputed static scale
too (tells whether input_scale calibration would reclaim the overhead or
it's construction/launch cost), and draws the verdict from the LARGE-M
run only, with explicit advice to prefer scope 'video blocks' when the
fast path is active but end-to-end looks flat.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First live diagnostics run showed the whole quantized path at 0.26x of
bf16 despite a healthy cuda backend and zero emulated layers. The single
combined timing cannot say WHICH half loses: the dynamic input
quantization wrapped around every matmul (a fixed per-call cost times
1232 layers/step) or the FP4 GEMM kernel not engaging. The probe now
times them separately at M=1024 and M=16384 (realistic video token
count) and names the culprit in the verdict.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'Loaded fine' and 'runs fast' are different claims. Every NVFP4 load now
prints a diagnostics block that settles which one you have:
- comfy_kitchen backend census (the big one: torch built for CUDA < 13
disables ck's CUDA backend, so quantized matmuls silently fall through
to the pure-PyTorch eager backend - correct output, ZERO speedup, with
an explicit warning line when detected)
- torch/cuda build + device capability
- loaded-model layer census: nvfp4 layers vs layers flagged
full-precision emulation (pre-Blackwell GPUs)
- a timed 4096x4096 GEMM probe (incl. dynamic input quantization):
bf16 vs nvfp4 wall time with a FAST PATH ACTIVE / NO SPEEDUP verdict
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>