sessionResetKeys() read scene_refinement_keys from the cached preview,
which can lag the prompt (debounced refresh; _distributeGlobalPrompt
carries the old keys forward until a follow-up preview). A just-added
key-bound shortcut was therefore absent from the reset list, so the modal
only offered to wipe "default". resetStudioSession() now awaits
refreshPreview(true) before reading the keys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A node input exposed to the main editor snapshotted its combo choices at
expose time, so refreshing the model list updated the live spec in the
Models menu but left the exposed project-settings dropdown stale (a new
LoRA showed up in node settings but not in the exposed control).
Add refreshExposedChoices(): after a refresh reloads specs, re-pull each
exposed control's and shared link's combo choices from the fresh spec and
persist (so the inspector re-renders via funpack-models-changed). Route
the modal button, ModelsModal.refresh and the menubar item through one
shared doFullRefresh().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Attribution was re-splitting the raw prompt independently and comparing
scene counts, then guessing on mismatch (all-keys union -> cross-training,
or empty -> keys not detected, incl. single-scene-with-keys). Both the
80MB cross-training and Darki's "custom uploaded keys not detected" trace
to that.
resolve_scene_refinement_keys now reads each scene directly: split with
the shortcut-aware split_timeline_verbatim (same scenes generation uses)
and collect, via new refinement_keys_in_text(), the keys whose shortcuts
fired in each scene (anchor keys apply to all). No scene-count compare, no
union/empty fallback except a genuine multi-scene misalignment. Single
scene with a custom key is detected; each scene gets exactly its keys.
Reverted the original-text scan added to prompt_scene_shortcut_keys (it
desynced that splitter from parse_timeline/generation and caused the
detection regression); it's now used only for session-reset all_keys.
Training law: a scene owned by custom key(s) trains ONLY them; the project
default key is left untouched and learns solely from scenes with no custom
key. Tests added for all three behaviors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrong-* ratings (appearance/details/action/combos) are prompt-REPAIR
signals — "good gen, wrong words/identity" — not quality rewards. They
were feeding their 0.0/low reward into both value-function training and
the keyless Absolute taste store, stamping visually-good conditioning as
a low-value valley. The VF ascent (now always the default key, applied to
every scene) then pushed conditioning away from good regions into drift /
wrong-character / distorted gens; Absolute stored good gens as global
bad-taste.
Wrong-* now carry skip_value_function: still drive prompt repair + the
relative per-key direction/category memory, but train no reward asset.
Quality ratings (Missing-*/Perfect/Nailed/Awful) unchanged. Gated all 3
relative VF call sites + _v2_learn_absolute. Preventative only — keys
poisoned before this fix still need reset/retrain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-scene key attribution diverged from the generation split whenever the
advisor rewrote prompt_to_encode away from the raw prompt, so
resolve_scene_refinement_keys hit its all-keys UNION fallback and assigned
every key to every scene. That union got persisted into the run and read
back at rating time, training every key on every clip (value_fn bloat).
- prompt_scene_shortcut_keys now scans both expanded and original text
(mirrors split_timeline_verbatim), so identical structure -> matching
scene count -> precise attribution far more often.
- Divergence fallback is now default-key only (empty sets), not the
all-keys union; a diverged run under-trains instead of cross-training.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The timeline clip volume slider writes audio_volume via patchSceneQuiet, which
updates state WITHOUT a re-render, so the player's cached clip.vol stayed stale.
Dropping to 0% only "stuck" when some later action happened to rebuild the
clips; dragging back above 0 never rebuilt, so the clip stayed muted. _applyFx
now reads the scene's audio_volume (and audio_separated) LIVE from the store; it
runs every frame during playback, so the slider tracks immediately both ways.
Final-render volume already read live, so only preview was affected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dropping an image on empty timeline did nothing — images only set an existing
clip's i2v anchor. The track already had drop-to-create for video; extend it to
images: a dropped image now becomes a new GENERATIVE scene at the project default
length, anchored to that image (i2v), with an empty prompt to fill in. New
store.addImageClip mirrors addScene (server assigns the id) plus the anchor.
Also stop the clip-level media drop from bubbling to the track drop, so dropping
an image ON a clip sets that clip's anchor without ALSO spawning a new clip (the
same latent double-fire existed for video). Media tooltip updated for discovery.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworks the rating picker wording in both the Studio node and the Movie Editor so
testers pick the right rating. Renames are DISPLAY-ONLY — the canonical label sent
to the backend is unchanged, so node validation and previously-saved ratings keep
working with no aliases/migration:
Missing quality -> "Bad quality / artifacts" (body horror lives here)
Wrong details -> "Not-asked details"
Wrong appearance-> "Wrong character/subject" (NOT body horror)
Descriptions now state that "action" covers CAMERA moves and "details" covers
appearance / setting / camera framing, matching _v2_axis_categories
(action->{action,camera}; details->{details,appearance,environment,camera,action}).
Stored values render via the new display map in formatLabel / getLabel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-scene multi-key steering ran the value-function ascend+search inside the
per-key loop, then averaged. That injected independent search noise per key and
let a poorly-fit fresh key's VF ascend toward a bad optimum, which the averaging
baked in — a body-horror path. Now each key contributes only its learned
directions (per-prompt conditioning memory, already delta-capped); the merge is
formed and magnitude-disciplined, then a single ascend+search runs on the merged
result using the always-on project default key (the most-trained, most stable VF).
Keyed single-key scenes also now ascend with the default VF rather than that one
key's, for the same stability reason.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-scene multi-key steering averaged each key's independently steered (and
VF-ascended/searched) conditioning with no magnitude discipline. Averaging
tensors that point different directions shrinks per-token magnitude — and
collapses it toward zero when keys disagree — leaving multi-key scenes weaker and
further-drifted than the validated single-key path, which free-ran into warped /
body-horror output (worst on simple prompts, fresh keys).
Re-impose the single-key tail on the merged result: cap the delta vs the raw
scene encode to 8%, then renormalize to its per-token norm. Capping before renorm
is what keeps a near-zero (disagreeing-keys) mean from being amplified into noise;
single-key scenes are unchanged. Does not address the separate question of
ambiguous "wrong appearance" ratings poisoning a key's value function.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The key name is now the filename: a key "123" is refinements/123.json, listed by
stem and resolved directly by name everywhere (Studio, loader, Editor, sampler).
This fixes shortcuts spawning ghost keys — a dropped/loaded <key>.json is found
by the refiner's own path lookup with no scanning or adoption needed. Sidecars
keep a <key>.<mode> suffix (value function, sampler context, latents); only keys
that aren't a valid filename fall back to the old <prefix>_<md5> hash.
Clean break: legacy hashed files are not migrated (and are hidden from the key
list rather than shown as hash names). Exports now download as <key>.json so a
re-dropped export is already canonical — no tolerant filename/inner-field
reconciliation layer needed.
Import now prompts overwrite-or-cancel on a name collision (409 -> confirm ->
retry with overwrite), in both the Editor and the Refinement Key Loader node.
413 chunked upload (Vast.ai / Runpod proxy body caps) retained.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Detection: refinement_key_names() now scans every .json in the refinements
folder (not just the canonical refine_v2_<hash>.json name) and tolerates the
{"state": ...} export wrapper, so manually-placed keys and HuggingFace downloads
are listed whatever they're named. On detection/load they self-heal: the file is
migrated to the canonical hashed path so loads (which resolve by hash) succeed
everywhere — fixing keys that listed but wouldn't load. Sidecars (sampler_ctx
etc.) are excluded by the clip-namespace check.
Upload 413: large keys POSTed in one shot were rejected by the reverse proxies
on Vast.ai / Runpod (body cap well under ComfyUI's 100 MB). importRefinementKey
now streams big keys in 256 KB byte-accurate chunks reassembled server-side via
new /refinement_keys/import_chunk + /import_finalize routes; small keys keep the
fast single POST with an automatic chunked fallback on 413.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A replacement is a prose phrase and may contain commas (e.g. "The video
rapidly cuts, showing the next view."). Both the Movie Editor's splitLines
and the backend _shortcut_replacements string fallback split on commas, so
one phrase became several variants: the editor showed it across lines,
expansion picked one half at random (never matching the comma-form transition
trigger), and a comma edit reverted on save because it re-split every time.
Replacements are now one-per-line only (commas preserved); triggers still
accept comma-separation per their UI. Existing torn replacements self-heal on
the next edit + save.
- mediabrowser.js: separate splitTriggers (\n or ,) and splitReplacements (\n).
- templates.py: _shortcut_replacements string fallback splits on \n only.
- tests: comma phrase stays one replacement (list + string + normalize paths).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replacing a scene's i2v anchor is a source-only change: it doesn't change
the prompt-preview key or selection, so commit() replaced state.project with
the saved project WITHOUT a notify(), leaving the stale optimistic DOM. The
clip's prompt text label could read blank until a reload/regenerate forced a
full re-render. Anchor/source changes now set _renderAfterSave so commit()
re-renders from the authoritative saved state once the save completes (the
flag survives commit's requeue/reentrancy and only clears on an actual
render). Backend round-trip already preserved text/gen_unit_id/source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
split_timeline_verbatim expanded shortcuts before detecting transitions, so
a cut marker that is ALSO a shortcut (e.g. qcut/cut, common now that cut
markers carry refinement keys) had its trigger word replaced before
detection -> the split was lost or misplaced ("scene 2 removed, scene 1
shows scene 2's prompt"). It now ALSO scans the original verbatim text for
triggers and merges with the expanded scan; trigger-shortcuts are caught at
their true offset, shortcut-driven splits and plain triggers are unchanged,
and the dedup collapses duplicates. Lossless round-trip preserved.
- conditioning.py: original-text trigger scan in split_timeline_verbatim.
- tests: user's qcut/cut case (2 scenes, actions 10|20,30, lossless) +
shortcut-driven split still works.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shortcut-internal-cut guard disabled the supported 'shortcut whose
expansion resolves to a transition' splitting (see project prompt model),
so scenes stopped splitting at all. Reverts split_timeline_verbatim to the
prior behavior; the real drop/shift bug needs a targeted fix with the
user's actual prompt + shortcut definition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
split_timeline_verbatim detected scene cuts in EXPANDED text and projected
them back onto the original. A cut living inside a shortcut's replacement
got projected onto the shortcut's edge; colliding/early boundaries were then
deduped away, dropping a scene and shifting the rest ("scene 2 removed,
scene 1 shows scene 2's prompt") until reload. This surfaced more once
shortcuts could carry refinement keys (more key-bound shortcuts in prompts).
The editor treats shortcuts as opaque (they only expand for preview/
generation), so the verbatim splitter now ignores any transition match that
starts inside a shortcut expansion. Literal user-typed markers still split;
generation still expands and splits unchanged. Result round-trips losslessly.
- conditioning.py: _starts_in_shortcut guard in split_timeline_verbatim.
- tests: shortcut-internal cut is ignored + lossless; literal cuts still split.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-scene multi-key learning trains every refinement key whose shortcut
fired in the prompt, so a reset must clear them all — not just the
project/default key, which left stale per-key state behind.
- conditioning.py: _v2_reset_prompt_keys() — on reset_session, union the
non-default keys from the raw positive + intent prompts and reset/save
each (primary key excluded; main flow already handles it). Reported in
the state status string.
- store.js: "Reset Studio session" now confirms before arming, listing
every key it will wipe (default + non-default keys in the prompt, from
the preview's scene_refinement_keys). Disarming does not re-prompt.
- tests: union/primary-exclusion + no-op cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface, before generation, which refinement key(s) each scene will steer
with — exactly matching the generation-time resolution.
- conditioning.py: extract resolve_scene_refinement_keys() as the single
source of truth; FunPackVideoRefinerV2._v2_scene_refinement_keys now
delegates to it (no behavior change).
- bridge.scene_refinement_keys(): preview payload per scene — explicit
non-default keys, or the project default key when none fired.
- server /preview: attach scene_refinement_keys (both gen and non-gen
branches), resolved against the project's refinement_key.
- inspector.js + styles.css: per-scene key chip in the timeline preview
(avg when >1; greyed "(default)" fallback).
- tests: resolver/Studio parity + bridge preview payload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Shortcuts carry an optional refinement_key (templates model + expansion);
editor gains a "Use non-default refinement key" checkbox + field.
- Project-level Refinement key field (Studio engine card) wired through
server -> builder into FunPackRefinementKeyLoader (settable in any mode).
- Per-scene key attribution (prompt_scene_shortcut_keys): keys whose shortcuts
fired in a scene; anchor keys count for every scene; union fallback on
rewrite/count mismatch.
- Per-scene conditioning merge: steer with each participating key's state and
average (one key = substitute, none = default), replacing the default-key
relative steering for keyed scenes.
- Multi-key rating: full per-scene learning suite + value fn trained into each
participating key's state.
- Tests for shortcut key surfacing, expansion tagging, per-scene attribution,
fallback, and keyloader injection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verify saved project matches the editor before generate and reject mismatches on the server. Invalidate preview-segment cache on regen, key trims by source mtime, and refresh the player after new renders land.
Flush pending global prompt edits before save/generate so runs use the latest timeline text and anchors. Improve live preview wiring detection, progress polling, overlay stacking, and diagnostics when a VAE is configured.
Expose Chain Sampler live preview as an optional VAE port so Video VAE and custom slots can select it in Models without Full control.
Co-authored-by: Cursor <cursoragent@cursor.com>
Show node class in the picker, allow manual wire to Chain Sampler live preview from Models, and keep wires synced with the View menu config.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire an optional fast preview VAE to show per-scene animated previews in the program monitor during generation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Respect frames_mode/fps_mode in effective duration, propagate frame clears for image-source scenes, and include project timing in ViewBus fingerprints so the timeline rebuilds when project settings change.
Co-authored-by: Cursor <cursoragent@cursor.com>
Document the Movie Editor, update README for the 3.0 frontend direction, credit pipeline dependencies, and ship CHANGELOG/pyproject 3.0.0.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drag clips apart to create pauses automatically; dragging back flush removes them. Guard preview minibar against gap segments so transport controls load.
Co-authored-by: Cursor <cursoragent@cursor.com>