Commit Graph
399 Commits
Author SHA1 Message Date
digital-garbageandClaude Sonnet 5 dde02f5cd7 fix: phrase blend -- shift the whole prompt, not just the phrase's own rows
Confirmed on the rental: the previous version (phrase-scoped shift) showed
zero visible effect even at strength 1.5. Qwen's rows are contextual, so
every word in "a cat sits on a mat" already carries "sits", not just the
verb's own row -- editing 1-4 rows out of the sentence left the rest still
pointing at phraseA's action. Now shifts the whole prompt-token range toward
the alt sentence's own whole-prompt mean.

Multiple [a|b] spans in one prompt are additive: each span's delta is
measured against the original conditioning, not against a prior span's
already-shifted result, so they don't compound order-dependently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:33:00 +03:00
digital-garbageandClaude Sonnet 5 8a6ab12ce6 feat: phrase blend -- per-blend strength override, [a|b:0.8]
Default 0.5 shift left phraseA dominating the video. [phraseA|phraseB:N]
overrides strength per blend. Fixed a shared regex gap along the way: every
weight/strength markup in this file (not just blend) silently rejected
leading-dot decimals like ".8" -- fell through to literal text with no
error, reproducing the exact "override did nothing" bug this feature exists
to fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:05:22 +03:00
digital-garbageandClaude Sonnet 5 38f059b889 fix: phrase blend -- alt phrase can't smuggle markup, can't double-apply on the wired path
Adversarial review of the previous commit found two real leaks and fixed
them: a colon in an alt phrase reached the real text encoder unstripped
(no bracket survives to re-catch it after substitution), and a wired
conditioning re-blended a second time because the re-tag guard only
checked for tag absence. Also reports per-span placement failures instead
of only counting successes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 21:05:18 +03:00
digital-garbageandClaude Sonnet 5 c6eab10c25 feat: [phraseA|phraseB] blends two H3 actions -- mean-vector shift, both encode paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 20:40:07 +03:00
digital-garbageandClaude Opus 5 8b680dc4aa revert: latent sharpness -- visible but bad at every strength on the rental
A latent unsharp on H3's 24-channel latent does not translate to picture
sharpness. Wrong mechanism, not wrong number; removed rather than tuned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 02:19:12 +03:00
digital-garbageandClaude Opus 5 0b4ca1eb46 feat: latent sharpness -- spatial unsharp on the late x0 predictions (KSampler branch)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 02:08:19 +03:00
digital-garbageandClaude Opus 5 bc0da79ffc feat: typed (phrase:1.5) weights reach the H3 attention bias
The parser existed but nothing called it; the syntax went to Qwen as text.
One pass strips both markups so timed and weighted spans share one clean text;
both encode paths carry them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 01:46:26 +03:00
digital-garbageandClaude Opus 5 b3a4af3b39 feat: Studio liked/bad/axis directions fade old ratings the same way
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 01:16:52 +03:00
digital-garbageandClaude Opus 5 9a3ea8c35c fix: timed phrases reach the sampler when a wired conditioning owns the prompt
The editor strips the brackets before the encoding node sees them and hands the
windows to Studio as char spans on the text it gave that node.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 00:58:02 +03:00
digital-garbageandClaude Opus 5 25e5cc8de0 feat: timed phrases use square brackets so shortcut parentheses can sit inside
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 00:49:16 +03:00
digital-garbageandClaude Opus 5 1a1ca7a31e feat: H3 timed phrases -- (phrase@t0-t1) shapes the video only inside its window
Query rows are attended in chunks split at window edges, each with its own key
bias: exact, no SxS mask, no extra model call. Outside the window the phrase is
masked from video rows (H3-World single-egress); audio and text rows never see a
window. Composer lists the windows under the prompt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 00:34:24 +03:00
digital-garbage 41547469a8 fix: H3 query steering silently defeated by h3_av_decouple, and a misreported commit outcome
Two real faults found by a second adversarial review round on 608a97a:

- h3_av_decouple, installed after q_steering, becomes the OUTER
  optimized_attention_override and always calls its inner override with a
  non-None mask for the video-row partition -- exactly the case q_steering's
  own override treats as "already handled, back off". With both enabled,
  q_steering captured and injected nothing while still printing "applying"
  and paying the clone/hook cost. The two can't currently compose, so
  q_steering is now skipped entirely (with a declared reason) whenever
  h3_av_decouple is also active, rather than silently no-op.
- conditioning.py's q_steer commit call was silent on no_pending/no_key,
  justified by a comment claiming REINS' own commit is silent there too --
  that was wrong, REINS is loud on exactly those outcomes. Fixed to match.

Also tightened the shape-mismatch warning's wording: it checks total element
count, not the heads/head_dim split, so a same-total-different-split
mismatch would still reshape onto the wrong head boundaries undetected --
documented as a known narrow gap rather than fixed, since a real fix would
mean changing the persistence format REINS itself also depends on.

Added regression tests for the av_decouple interaction (both directions:
guard fires when it should, stays quiet when it shouldn't).
2026-09-07 04:10:21 +03:00
digital-garbage 299941a22e feat: H3 query steering -- REINS-style direction learned and applied on Q, not hidden state
Same liked-minus-disliked mean-difference as h3_repr_steering, captured off Q inside
optimized_attention_override instead of a block's hidden state, own kind="q_steer" sidecar
so the two vector spaces never collide. Only Q is biased: a uniform additive bias on K is a
provable softmax no-op, and on V it's mathematically identical to h3_repr_steering's own
mechanism, so neither is a genuinely different lever. Off by default (blank block spec).

h3_repr_steering.py's persistence functions take an optional kind= param (default unchanged)
so this reuses capture()/direction()/commit() verbatim instead of a parallel implementation.
2026-09-07 03:43:04 +03:00
digital-garbage 4aad3d7a09 refactor: block-influence data lives in Settings, not the console
Research data belongs in Settings > Refinement & Taste — readable,
downloadable, clearable, with its own collection switch — not in a run
report that already carries more than anyone but us needs.

- Removed the sampler widget and its Editor knob entirely. Collection is
  driven by a global switch (env var live for this process + on-disk copy
  so a fresh rental comes back recording), the same two-layer mechanism
  trajectory_probe uses.
- Removed the per-rating console readout. Findings go to the panel;
  failures still speak through funpack_log.
- New section in Refinement & Taste: toggle, recorded count, Download,
  Clear, a plain-language flatness verdict, and the busiest blocks ranked
  with their liked-vs-disliked delta.
- Routes: GET/POST /api/block_influence, /export, /clear.

The switch gates whether the PROBE is installed and nothing else —
save_pending/commit stay unconditional, with a test pinning that, since a
toggle that quietly stops recording ratings is a mistake this project has
already made once.

Verified live: routes round-trip, the panel renders a seeded 6-run
profile, and a planted rating-correlated bump at block 31 surfaces as
+0.0107 in the UI. Seed data cleared afterwards.
2026-09-04 17:13:35 +03:00
digital-garbage d6981036d0 feat: per-block influence probe for H3 (measurement only)
Records ||f_i(x_i)|| / ||x_i|| on video rows for every block, averaged
over each run's steps, and pairs the profile with that run's rating.
Answers the question that gates rating-driven block weighting: is H3's
depth profile flat (nothing to aim at) or structured? Reports a
scale-invariant flatness number, and once a key has 2+ liked and 2+
disliked runs, which blocks run hotter on the liked ones.

Steers nothing. Installed BEFORE any steering hook so the delta it
records is the block's own, not one already containing an injection.
Bounded cost: two norms per block per step on ~512 strided rows, no
extra forward pass, results accumulated as device tensors and synced
once per run rather than 50x per step. Says so explicitly when it
records nothing, since an empty capture and a flat profile are
otherwise indistinguishable downstream.

Adjacent to per-block residual steering, removed in b7ed4f9 for
finding no rating signal above the 4th decimal -- that pass had no
per-step resolution, which its own removal note named as the gap.
This either reproduces that null definitively or it doesn't.
2026-09-04 17:01:21 +03:00
digital-garbage b7f15a1a6b feat: re-enable DynaShift on H3, add a positive-pull bank alongside
the negative one

DynaShift was forced off for H3 in the 2026-09-03 disable pass on
theoretical grounds (LTXAV-calibrated, never actually tested on H3) --
re-enabled here to find out. Also gives it a second bank: negative_memory
now stores liked generations the same way it already stores disliked
ones (is_positive_profile, same sign-only convention h3_repr_steering
uses), and _build_dynashift_wrapper computes mean(liked) - mean(disliked)
over each bank's matching-resolution frames as a pull direction --
deliberately NOT nearest-neighbor matching toward one liked example,
which would risk reproducing that specific past generation. Needs
MIN_BANK_FOR_PULL (2) entries in each bank at the run's own latent
resolution before it contributes anything.

Reuses the existing dynashift/dynashift_strength/dynashift_threshold
controls for both directions -- no new Editor UI needed.
2026-09-03 23:22:35 +03:00
digital-garbage e079c01c09 revert: remove prompt_hash cross-prompt grouping from block_sweep
Never worked on this R2V-wired setup (prompt_hash always came back
None, silently disabling the guard on every sweep) and fixing it was
explicitly declined. Removed rather than left inert -- a guard that
never engages is worse than no guard, since it implies a protection
that was never actually there.
2026-09-03 21:21:35 +03:00
digital-garbage 9c0dac7076 revert: remove the REINS capture-pause toggle entirely
Misread "disable that .pt tracking" as "stop recording ratings" --
the actual ask was about console noise, already handled by removing
the block-sweep print. This toggle instead silently stopped every
rating from being recorded while it was on, confirmed live: a real
key sat at "0 liked / 0 disliked" through multiple ratings with no
visible error, and the "committing weight" log line looked correct
the whole time (fixed separately, but the toggle itself should never
have existed).

Removed cleanly rather than patched: capture_enabled/set_capture_enabled,
the enabled gate in save_pending/commit, the /enabled route, the API
client method, and the Settings toggle row. save_pending and commit
now behave exactly as they did before this was ever added -- every
rating is recorded, unconditionally.
2026-09-03 20:17:31 +03:00
digital-garbage a2da292f52 fix: commit() reports whether a row was actually recorded, not just the reward
"Committing weight +X" printed unconditionally, before checking
whether there was a pending capture to attach it to -- so a correctly
computed reward with nothing to pair it with (capture was off, or the
sampler's h3_repr_steering toggle was off, on the run being rated)
looked identical in the log to a real commit. That's the gap that
made "0 liked / 0 disliked" on a cleared key impossible to diagnose
from the console alone: the weight line looked right every time.

commit() now returns "recorded" | "no_pending" | "disabled" | "no_key"
and the caller prints which one actually happened.
2026-09-03 20:11:34 +03:00
digital-garbage 329e2ec057 fix: stop printing the block-sweep ranking to console every rating
Duplicated what Settings > Refinement & Taste already computes and
shows as a table on demand. Console still gets the compact one-line
commit confirmation; the full per-block permutation-test ranking now
lives only in the panel.
2026-09-03 19:30:55 +03:00
digital-garbage 4af9875d15 feat: H3 block-depth sweep + Settings export/clear UI for REINS
Captures every candidate block (5,10,...,45, plus DEFAULT_BLOCK=25)
read-only alongside the one that actually steers, so block_sweep()
can answer "which block really separates liked from disliked" from
data already being collected -- reusing trajectory_probe's own
permutation test (proven correct on this project's real data) instead
of trusting REINS' 50%-depth heuristic (their own paper picks the
best layer per model by sweep, not by applying one universal
percentage) or porting a diagnostic built for a different question
(LTXAV's identity-block search measured cross-scene consistency, not
liked-vs-disliked separation).

Grouped by prompt_hash (trajectory_probe's own, reused) so cross-
prompt content differences can't masquerade as a rating signal -- the
same confound that inverted the first cross-prompt read of this
project's output value function.

Settings > Learning > Refinement & Taste gets a per-key REINS panel
(status, block-sweep table, export/clear) mirroring the trajectory
probe UI, plus matching /api/h3_repr_steering routes -- so data can
be pulled off a rental and handed over directly instead of relayed
through pasted console logs.
2026-09-03 19:26:30 +03:00
digital-garbage 3e5c07249a fix: close a gap in the H3 disable pass — output_guidance snapshot and DynaShift capture
Both were captured "regardless of the toggle" by original design (so
turning guidance on later works immediately), which meant they kept
running on H3 even after output_guidance/dynashift were forced off —
exactly the kind of function the "disable everything unrelated to
REINS" pass was supposed to catch, missed because the capture point
predates any toggle check.

Confirmed live: the user's own log showed "DynaShift negative bank
updated — 1 entry" after the rest of the disable pass had already
landed.
2026-09-03 18:59:47 +03:00
digital-garbage 85add9ed84 fix: H3 scale ratings were silently discarded and replaced with the default
Root cause of "every rating counts as liked": the 'rating' node
input is a COMBO widget the graph builder validates against
V2_RATING_LABELS (builder.py's _live_value) -- the same guard that
falls back to a node's default when a saved LoRA/model choice isn't
installed on this machine. The H3 scale sends bare digit strings,
which were never added to that list, so EVERY H3 rating since the
scale shipped was silently rejected as "not a valid choice" and
replaced with the node's declared default, "Missing action" (+0.05)
-- explaining the diagnostic log exactly: legacy_score=None (never
reached the digit-parsing path at all) and reward +0.050 (the
literal default, not a mapped value).

Confirmed with the log line the user captured: the fix earlier this
session to normalize_refiner_v2_rating's digit math was correct and
irrelevant -- the real rating value never reached that function.

Fix: add "1".."10" to V2_RATING_LABELS so they pass validation
unchanged, same list both rating widget declarations already read.
2026-09-03 18:56:28 +03:00
digital-garbage 90250e9f6a debug: log the weight REINS actually commits, and which rating produced it 2026-09-03 18:52:24 +03:00
digital-garbage 6a942ae3a9 feat: disable every rating-driven mechanism on H3 except phrase emphasis and REINS
H3's only validated (or being-validated) rating consumers are
h3_phrase_emphasis and h3_repr_steering. Everything else --
value_guidance/absolute taste steering, output_guidance,
trajectory_guidance, dynashift, embed_guidance, score_slider,
taste_nearest_prompt, and the path planner -- was built and
calibrated for LTXAV or for a value function nothing on H3 reads.
None of it helps REINS; it only trains value functions that are
never consumed and prints status lines for mechanisms that can
never fire, which is what actually produced the "80% of the log is
useless" bug report this responds to.

Forced off at two chokepoints (top of sample() in samplers.py once
self._is_h3 is known; top of refine_v2 in conditioning.py once the
clip's family is known), not just hidden in Engine Settings, so a
project saved before this still behaves correctly. Value-function
and Absolute-taste TRAINING (not just application) is skipped too --
per-scene, per-key, and the main path -- since nothing on H3 will
ever read what they'd produce.
2026-09-03 18:46:30 +03:00
digital-garbage c8993891c6 fix: H3 scale's reward sign was backwards for 5 and 6 out of 10
normalize_refiner_v2_rating's legacy digit path snapped a bare number
to the nearest existing category label and used THAT label's reward
verbatim. Missing action's +0.05 was calibrated for its own meaning
("the frame was fine, just no motion") -- not "a mediocre score" --
but 5 and 6 both landed in that bucket, so rating something a 5/10
produced a POSITIVE reward. Every h3_repr_steering weight built from
those ratings was backwards.

Reward is now a straight linear map across the full 1-10 range
(1 = -0.9, 10 = 1.0), so the sign matches what the scale promises.
Level/missing_axes/skip flags still come from the nearest label --
harmless, since nothing needs them exact for a legacy score.
2026-09-03 18:42:59 +03:00
digital-garbage 3ba114708b feat: h3_repr_steering learns a weighted direction, not a hard liked/disliked split
Every rated run now pulls proportionally to its own reward instead of
being bucketed into two equal-weight groups -- a 9/10 counts for more
than a 6/10, instead of both counting as one full vote. Centring the
WEIGHTS (not the descriptors) before the weighted sum is what makes
shared content cancel exactly even when the weights are unbalanced;
see the module docstring for the identity. This is what actually
justifies the scale existing at all, rather than just picking one of
two buckets with extra steps.

Also: the H3 scale's heart/loved button did nothing --
normalize_refiner_v2_rating only applies the loved boost inside the
branch that matches an existing category label, and a bare number
never reaches it (confirmed: '7' and '7|loved' produced the identical
reward). Removed it from the scale rather than wire a boost with no
established meaning on a 1-10 input.
2026-09-03 18:30:17 +03:00
digital-garbage 33038ae700 feat: H3 rating picker becomes a 1-10 scale; REINS learns from rating identity, not reward sign
The category grid assumes axis-specific effects (phrase emphasis,
dynashift...) this project has not proven land differently on H3.
Replaces it with a single scale for H3 pipelines only -- the backend
already turns a bare number into the nearest existing rating profile
(normalize_refiner_v2_rating's legacy-score path), so every consumer
(value functions, phrase emphasis, dynashift, path planner) keeps
working unchanged; only what the picker sends changes.

Also fixes h3_repr_steering: it was splitting liked/disliked by the
sign of the blended scalar reward, which counts near-misses like
Missing action (+0.05) as "liked" even though that reward exists for
the value functions' quality-landscape ascent, not as a liked/disliked
verdict. Now filters by rating KEY (LIKED_KEYS/DISLIKED_KEYS) and
excludes everything ambiguous instead of guessing a side for it.
2026-09-03 18:22:11 +03:00
digital-garbage 6e28f56c92 feat: REINS-style representation steering for H3 (experimental)
Reaches inside the forward pass instead of around it: captures the
video rows of one block's hidden state per generation, and once a
refinement key has 3+ liked and 3+ disliked runs, adds their mean
difference back into that block's output on every later run. No
gradient, no MLP -- a mean-difference of vectors is the whole model.

Masked to video rows only via the block's own mod_segments tags,
never audio or text -- the one documented failure of this class of
intervention (project_self_consistency_failed.md) happened on a
different model (LTXAV) via joint audio-video attention.

Off by default, H3-only, unvalidated.
2026-09-03 17:50:37 +03:00
digital-garbage c86e8894f6 feat: variability dial on H3 phrase emphasis
0 = only cemented phrases get extra attention. 1 = that bias
collapses to nothing, letting untrained content compete evenly.
Same attention-bias channel as the existing toggle, scaled down.
2026-09-03 16:58:34 +03:00
digital-garbageandClaude Opus 5 d392578832 feat: steering that reaches the first half of a generation
Every rating-driven mechanism shares one gate that opens only over the last half
of the schedule — after motion and layout are decided — so a rating about
movement has never had a window to act in.

This learns one value function per schedule quarter, each trained on that
quarter's own predicted output, each steering inside its own window, with no
shared late ramp. It reads the probe's log, so it starts trained on whatever has
already been rated.

Basis: 51 rated runs across two independent sessions, different prompts and
balances. The early window measured 88% and 89% of the late window's effect,
direction unanimous across all 8 bucket-by-session results, sessions combining
to p 0.043. Cross-validated on the clean session the heads rank held-out runs at
AUC 0.73-0.82.

Beside output_guidance, not replacing it, and off by default: the honest test is
the same seed with it off and on. A window with under 10 samples is byte
identical to the feature being off, so "nothing changed" stays readable, and the
run says which windows can steer rather than only that it is on.

Not included: axis routing (quality late, action early). It is the next step and
it would muddy this A/B.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 14:29:17 +03:00
digital-garbageandClaude Opus 5 39d0f312de feat: trajectory probe — does a rating show up before the halfway point?
Every rating-driven mechanism shares one gate (_make_steer_ramp) that opens
only over the last half of the schedule, so a "Missing action" rating has no
window in which it could act. Banking x0_hat per schedule position would give
it one — if the early half carries any signal. This measures that.

Off unless FUNPACK_TRAJECTORY_PROBE=1. Records, never steers. Built for H3:
buckets clamp to few-step runs, batched predictions are refused rather than
averaged with their unconditional row, and each pass binds its own schedule.
Read the result with tools/trajectory_separation.py.

fix: reference numbering counts within its own kind

A reference slot resolves per kind, so marking an image then a video labelled
the video R2 while the only slot reaching it was "Reference video 1" — there
was no way to have an R1 video and an R1 image at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 15:35:41 +03:00
digital-garbageandClaude Opus 5 b16d48b2db maint: finish prompt repair, and label bounded attention untested
Prompt repair lost most of itself in an earlier stage; what was left was a
fallback that appended missing phrases back onto the prompt, reachable only
through a list nothing fills any more. Gone. The axes stay — active_repair_axes
is the "what's missing" signal the advisor reads, and it never edits a prompt
by itself.

prompt_repair and prefer_wired_conditioning are read by nothing at all and only
survive in old saved settings, so the card stops printing them.

Bounded attention is built and has never been run, and its left/right split is
fixed. Both labels now say so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:41:10 +03:00
digital-garbageandClaude Opus 5 aebc286042 maint: remove the H3 render gains
Six widgets (three write gains, prompt loudness, taste push, learned/manual
mode) and the whole rating loop behind them. Never proved out on the rental:
picture gain past +-0.1 adds distortion and latent noise, the rest changed
nothing legible, and the audio gain was never worth testing.

The final-layer picture detail SURVIVES and now reads its own widget — it was
the only one of the set that showed a real effect. Relabelled experimental.

Two things fall out with them. The refinement key no longer holds any sampler
value, so key scoping has nothing left to scope and goes too. And the card's
ownership table now reads dependsVals as well as dependsValue: the Studio rows
use the plural form, so absolute_strength was printing under steer_mode
"relative", where it does nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:30:19 +03:00
digital-garbageandClaude Opus 5 edcedd9062 feat(h3): two more punctures — prompt timing, and picture detail that spares the audio
h3_prompt_time: the prompt is told how finished it is independently of the
picture, so the model leans on it from the first step instead of treating it
as unreliable while the picture is noise. One extra row through each block's
AdaLN projection, dropped before the result returns. Manual dial only — its
off value sits at the end of its range, not the middle, so the rating loop
cannot explore around it safely.

h3_video_detail: crisper or softer picture with the audio bit-for-bit
unchanged. The final layer is past the last attention pass, so unlike every
other picture control it has no path to the soundtrack.

apply_adaln_gains -> apply_adaln_edits; both block edits share one wrapper.
video_detail joins the rating-learned gains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:05:42 +03:00
digital-garbage 0597603980 feat(log): one line per feature, state then reason
[FunPack] K/V conditioning patch: Inactive | Non-LTX model loaded
    [FunPack] Chunk cache: Active | 22 chunks, 4 steps each, consistency

`feature(tag, name, active, reason)` is the shape, and `skipped` renders
identically — a deliberate skip and an inactive feature are the same fact and
should not have two spellings. `failed` becomes `Name: Failed | <effect>`,
keeping the effect rather than the exception text: "that explains what I am
looking at" is the half worth the line, and an exception message rarely says it.

Everything funpack_log prints is now cut at the first sentence end, so the call
sites keep their reasoning without spending it on every run. SENTENCE ENDS ONLY
— an em-dash introduces the reason as often as the essay, and a renderer that
guesses wrong eats the one part of the line worth reading. A single long
sentence is hard-trimmed instead of left to run.

FUNPACK_LOG=verbose (or set_verbose) prints the full text, exception included.

Converted to the new shape: the chunk cache, region locks, interior pins, the
taste push, the token-refiner edit, AdaLN gains, rating-learned gains, phrase
emphasis, the K/V conditioning patch, the scene split, Bounded Attention,
per-block LoRA weights, LTX enhancements. The rest of the log gets the trim for
free and keeps its own phrasing until converted.

Tests: 1815 python (+9), 68 JS.
2026-08-26 23:10:27 +03:00
digital-garbage 0f05d81db8 fix(studio): the enhancer generated twice on every Editor run
The Movie Editor's builder forces split_by_transitions on for EVERY project, and
the per-scene conditionings replace the base entry — so enhancing the base
prompt as well was a second full generation whose result was then discarded. At
the ~59s per call measured on the rental that is a minute of pure waste per run,
and it looked like thinking mode being stuck on.

It was not thinking: that defaults off and is passed explicitly, so it can never
inherit the repair advisor's own default of True. It was two code paths both
doing the work.

Now the base is enhanced only when the run does NOT split, and the skip says so
on the console rather than being silent. One generation per prompt that is
actually encoded.

Tests: 1696 passed (+6).
2026-08-26 17:48:07 +03:00
digital-garbage 5e9de5223c fix(studio): trim the enhancer by repetition, not by an invented length cap
The 2000-character cap was wrong. H3 has no padding, so every prompt token is a
real sequence row and a long prompt is a legitimate thing to want — an enhanced
one especially. Cutting at a fixed length contradicted the token limit the user
had just set.

Length cannot tell "long and detailed" from "stuck". What distinguishes them is
that a model which has stopped writing starts repeating itself, so that is what
is detected now: three occurrences of the same sentence and the prompt is cut
at the first repeat. A long prompt that never repeats is never touched.

The only remaining length bound is the one the user's own token limit already
implies (~4 chars/token), so there is one control rather than two fighting.
Short recurring lines are exempt — "She smiles." legitimately recurs in a shot
list.

Tests: 1690 passed.
2026-08-25 23:03:22 +03:00
digital-garbage 599e6a7fa8 fix(studio): the enhancer ran to the cap and never said where it ran
Reported from a rental: with the length limit raised to 8192 the enhancer used
all of it. The H3 encoder has no trained decoder head — ComfyUI falls back to
tied word embeddings — so it may never emit a stop token, and max_length stops
being a ceiling and becomes the actual length. Nothing downstream noticed: it
just encoded thousands of tokens of rambling as the prompt.

Now capped at 2000 characters and cut back to the last COMPLETE sentence, never
mid-word, and the status says the model did not stop on its own.

Also reported: it seems to run on CPU. Every generation now reports the device
it executed on, how long it took and the cap that applied — a 32B encoder
sharing a card with the DiT can be placed on the CPU by ComfyUI's loader, which
is minutes per call and invisible from the returned text.

Tests: 1685 passed (+9 enhancer).
2026-08-25 22:58:55 +03:00
digital-garbage 01400b4a2b feat(studio): an LLM enhances the prompt before anything is generated from it
Runs after shortcuts and after $variables resolve, before encoding — the model
is handed exactly the text that would otherwise have been encoded, and the
result is what ComfyUI generates from, so nothing downstream knows it happened.

Not the repair advisor: that one is triggered by a rating and fixes a named
fault. This runs on every generation, needs no key, no rating and no previous
run, and only elaborates. Uses advisor_clip when one is wired (the Studio node
already has the socket), the generation CLIP otherwise.

Multi-scene enhances each scene separately. The editor's scene list is
authoritative on count, so rewriting the scenes as one paragraph and
re-splitting could come back with a different number and desync every clip from
its anchor. One generation per distinct text, cached within the run.

Every failure path returns the ORIGINAL prompt — an enhancer that empties the
prompt on a bad generation would render a blank video and blame the model.
Thinking traces, code fences, preambles and whole-answer quotes are stripped;
quotes inside the prompt survive, because speech is quoted on purpose.

Settings > Engine > Studio: toggle, editable system prompt (new textarea field
kind), length limit, temperature, top-p, thinking. Off by default.

Tests: 1637 passed (+24).
2026-08-25 22:44:26 +03:00
digital-garbage 54fa8e4b43 feat(h3): region-locked anchors, and the taste direction lands after the refiner
Region lock: a keyframe pin can cover part of a frame. Transparent areas of an
i2v anchor are dropped from the packed sequence entirely — not blanked, not
noised, absent — so the model invents them. Row deletion re-derives the layout
from upstream's own output; two patches share one keep-mask so they cannot
disagree. Surfaced through the anchor's alpha, which needs no new wire and works
in the Editor. Excluded pixels are filled with the kept area's mean before the
VAE so no hard edge bleeds in. EXPERIMENTAL: fl2va saw whole condition frames.

Taste bias: the liked direction Studio already learns is now also added AFTER
H3's token refiner, where it lands as sent instead of being mixed across tokens
and normalized away. Relative to the span's own row norm, so one strength means
the same thing on every prompt. How far to push is a fifth learned gain,
centred on 0 and signed; h3_taste_bias is the manual override. Needs 3 liked
runs.

Gain state generalized to per-key centres, bounds and exploration width.

Tests: 1608 passed (+46). Untested on a rental.
2026-08-25 21:48:28 +03:00
digital-garbage 551363d1b8 feat(h3): each rating credits the gain it is actually about
The gains read only the scalar reward, so "Missing concept" — reward +0.10
because quality was fine — nudged every gain slightly TOWARD the run that
missed the prompt, and taught the audio gain something about a complaint
that had nothing to do with audio.

Every rating already carries three per-axis signals in [-1, +1]. Route
them: concept -> prompt + prompt_scale, detail/quality -> video, and audio
keeps the overall reward because no rated axis covers it.

The combiner is min, not mean. These axes are conjunctive — the video gain
has to satisfy detail AND quality — so a satisfied axis must not mask a
failed one. Averaging let it: "Missing details" came out at -0.08 for the
gain that governs detail. It is -1.00 now.

Recovering four parameters from one scalar worked, but only by waiting for
random perturbations to decorrelate. Each rating now teaches the parameter
it names.
2026-08-25 21:27:18 +03:00
digital-garbage ffe2a49a9e feat(h3): render gains are learned from ratings, manual is the opt-in
The four H3 render strengths shipped as hand-set dials. They should not
have: four scalars is a SMALLER search than the sigma profile, which
already converges on ratings alone, so typing them is the user doing work
the loop can do.

Same ES update as the sigma profile, with one difference — that profile is
centred on 0, so its applied value IS its perturbation; these are centred
on 1.0, so the perturbation is (applied - value) and that is what the
reward is credited to. Exploration narrows on a like, widens otherwise,
and is seeded from the run seed so a repeat repeats.

Learned values reach the sampler through the conditioning meta, the bridge
h3 token weighting already uses: Studio owns the key, the sampler owns the
application. h3_gain_mode defaults to "learned" and ignores the widgets;
"manual" uses them as typed and still works with no Refiner in the graph.

Learning runs on every rating regardless of the toggle, the same rule
value_guidance follows.

Also fixes four note_on_change calls added earlier this session that were
missing the required tag argument — they would have raised on the non-H3
and unreadable-tags paths.
2026-08-25 18:48:50 +03:00
digital-garbageandClaude Opus 5 6e99b0204f fix(h3): the emphasis line reports counts, never the phrases themselves
It named the three strongest learned phrases as examples. Phrase memory holds
the user's prompt, so every paste of the log carried prompt text out with it —
stripped by hand each time, which is a thing that will eventually be forgotten.

The strongest weight replaces them: it answers the question the examples were
there for (is emphasis doing too much) without quoting anything. Counts stay.

Adds a test on the SHAPE, not on one message — joining phrase text into a log
line is how this would come back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:42:05 +03:00
digital-garbageandClaude Opus 5 33227b198d diag(vf): say where the value functions trained, and how big they are
"Training is slow" and "training is slow ON THE CPU with four million
parameters" are different problems with different fixes, and nothing in the log
told them apart — leaving it to be inferred from timings, which is how the
first guess at this went to the disk instead.

One line per run per value function, naming the device and the parameter count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:34:26 +03:00
digital-garbageandClaude Opus 5 8cdee7072f feat(editor): Settings > Appearance gets "Show timeline on hover"
Off by default. On, the timeline sits as a strip — its header, so the title,
transport and Composer stay reachable — and opens to full height while the
pointer is on it or something inside it has focus.

It also opens for the whole of a DRAG. During one the pointer is in drag mode
and a strip that stays shut is a drop target you cannot hit, so dragenter holds
it open and drop/dragend closes it. The tracker counts depth rather than
holding a boolean: dragleave for the element being left fires AFTER dragenter
for the one being entered, so a flag closes the strip mid-drag as the pointer
crosses from one clip to the next.

The strip height is MEASURED from the header rather than fixed: .zone-head
wraps when the zone is narrow (34px min-height, 138px on a normal window), so
any constant clips the very controls the strip exists to keep visible. It is
re-measured on resize. flex: 0 0 auto matters — .zone is a shrinkable column
child, and a height with min-height: 0 let the workspace squeeze the strip to
1px. The body collapses rather than being display:none so the lanes are not
torn down and rebuilt on every open.

A browser preference like the colour scheme, not a project one, and registered
in the same Settings section for that reason.

Also in this commit:
- vf: train wherever there is a GPU, not only when the sample sits on one. The
  x0 value function's snapshot loads with map_location="cpu", so keying on the
  sample left it training on the CPU — the run that named it measured
  "x0 vf: train 10.1s" against 46ms for the same net locally.
- The stage line counted nested sub-stages twice and reported 153% of a run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:32:11 +03:00
digital-garbageandClaude Opus 5 78fdad9b29 perf(studio): split the value-function stages into load, train and save
Both value functions cost about eight seconds a run, and one has ten times
fewer parameters than the other — so it is not the training math, and the whole
per-call cost is somewhere else. The key file writes at roughly 3 MB/s on the
rental, which makes the checkpoint write the first suspect, but that is a guess
until the three phases are timed apart.

Also times loading the x0 snapshot, which is a full packed latent read from the
same disk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:13:54 +03:00
digital-garbageandClaude Opus 5 8e3c707c81 fix(sampler): a wired reference conditioning is not a second scene
The Chain Sampler reads one conditioning entry per SCENE
(scene_conditionings = positive[:max_scenes]). When Studio stopped discarding
the entries past the first of a wired CONDITIONING — the fix for a reference
surviving while the prompt did not — those entries reached the sampler
untagged, and the r2v reference block was counted as a scene of its own. The
run sampled the shot twice, which from outside is "the model ran a second pass
for no reason".

Studio tags every pass-through entry as a companion; the sampler excludes
companions when counting scenes and hands them to every scene instead, so they
still reach the model. Both halves of the original bug stay fixed: nothing is
dropped, and nothing is mistaken for a scene.

Also fixes the liked-conditioning blend, which was the last payload path still
doing a raw lerp: _v2_shape_compatible ignores sequence length by design, so a
direction learned at 586 positions met a 494-position prompt, threw, and took
the whole pull toward what the user rated well with it. Routed through
_v2_payload_like like every other payload.

Adds stage timers for the nine learning steps the first pass did not cover —
55% of a 26.9s run was accounted for, and the rest was unnamed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:06:52 +03:00
digital-garbageandClaude Opus 5 ce046fdfd8 perf(studio): time the learning stages, so a slow run names itself
The encoder tally answered "is it the encoder" — at 24.9s it is 2% — and the
next question is which learning stage the rest is. 2.4s at three rated samples
and 24.9s at five is not smooth growth, and the shape of that curve says
nothing about which stage draws it.

Times the six that can dominate a run: phrase memory, conditioning memory, the
three value functions, and applying the learned direction. One line, stages
over 0.05s only, costliest first.

Measurement only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:56:05 +03:00
digital-garbageandClaude Opus 5 3bb7b78557 perf(studio): keep the key's tensors in a binary sidecar, not as base64 in its JSON
One conditioning delta is 586x3584 floats — several megabytes of base64 inside
a document that is parsed and rewritten IN FULL on every generation. That is
the cost that grows with everything ever learned, and it is paid whether or not
any of those tensors are used.

Tensors now go to <key>.json.tensors.pt and the JSON keeps a named reference
whose id is the tensor's path in the tree ("state.global.liked_dir.direction"),
so the key stays readable and a reader can see what a reference points at.

No migration: a key written before this carries inline blobs, is read exactly
as it stands, and moves to the sidecar on its next save. If the sidecar cannot
be written the tensors stay inline — a slow key is fine, a JSON pointing at a
sidecar that does not exist is not. A reference the sidecar has lost reads as
no tensor, so a direction goes unapplied rather than breaking the run.

Deletion already sweeps it: delete_refinement_key globs every <key>.* sidecar
for exactly this reason. Session Reset removes it explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:54:01 +03:00