Commit Graph
374 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 1f54ad4519 fix: phrase blend -- an ordinary colon in a shortcut silently broke the whole construct
A real shortcut expanding to "wide shot: character walks" made [a|b]
never match at all, no error, because the previous fix banned ':' and
'@' anywhere in either phrase to stop one dangerous shape (an alt
phrase ending in ":1.5"). Two narrower shape-guards were tried and each
let a different shape through (colon-then-@, an em dash) while still
blocking ordinary prose. Reverted to fully permissive: a markup-shaped
alt phrase just reaches Qwen as literal punctuation in its own separate
re-encode, the same accepted trade-off this file already makes for a
weighted phrase's own stray parentheses -- a quality footnote, not a
leak, confirmed by tracing the encode path and its existing exception
handling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 21:35:37 +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 01c0a2b9c2 feat: H3 phrase probe -- where the picture reads a bracketed phrase, and where hiding it changes the picture
Measurement only, opt-in from Settings > Refinement & Taste. Reading map is free
(attention share on the phrase's keys per block); response map costs one extra
forward per phrase per model call. Phrases are numbered, never quoted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 03:18:34 +03:00
digital-garbageandClaude Opus 5 3cb10d3375 feat: [phrase:1.5] is a weight too -- brackets are the universal prompt markup
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 02:26:13 +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 af5bf69eb0 test: follow the phrase-emphasis log's new wording
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 01:36:11 +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 84aaf7084d feat: REINS direction fades old ratings (0.9 per rating, read-time)
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 aa4efe3248 fix: timed phrases may contain colons and @; only :number before the window is a weight
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 00:43:26 +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-garbageandClaude Sonnet 5 54e49884a4 feat: allow H3 attention temperature to sharpen, not just flatten
h3_explore_temperature was positive-only (flattens the softmax -- more
exploratory, pulled away from the prompt's own weighting). Same knob now
also takes negative values (widget min -0.9) to do the opposite: sharpen
attention so it commits harder to what the prompt already made confident,
instead of only ever being pulled away from it. Same code path (temperature
= 1 + strength, dividing Q before the dot product), sign decides direction.

Floored at 0.05 so an out-of-widget-range strength can't drive temperature
to zero or negative. Fixed a NaN gap the floor doesn't cover: `!= 0.0`
(needed so negative values install at all) is also true for NaN, and
max(1+nan, 0.05) is nan, not 0.05 -- excluded explicitly.

Editor UI widget (engine_settings.js) updated to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 23:14:28 +03:00
digital-garbageandClaude Sonnet 5 c929004573 fix: disable Comfy's model compiler around REINS-injecting/block-repeat forward calls
aimdo's malloc-graph compiler assumes every block-scope forward allocates the
same pattern call to call. REINS while actually injecting (not passive
capture) and block repeat both patch what a block's forward does, which
crashed on a real rental with "aimdo memory compile error" -- confirmed via
instance log: identical workflow succeeds in passive-capture-only mode
(injection branch never entered) and fails the moment strength > 0. This is
a known upstream conflict class (Comfy-Org/comfy-aimdo #16144, #16342), not
specific to REINS; their fix is --disable-comfy-compiler, read live per
malloc-graph scope rather than cached at startup, so toggling it in Python
around just the affected sample_custom call works without touching the
(templated, rental-fixed) launch command.

Gated on actual install success (model identity before/after each
installer), not on the raw request flags -- a silently-refused install
(non-contiguous span, no block list, zero strength) leaves a block's
forward genuinely unpatched, so forcing the compiler off there would be a
pure, needless perf/VRAM regression instead of a fix.

Also: FUNPACK_AIMDO_LOG env var to raise comfy_aimdo's own native log level
to DEBUG, for diagnosing this class of failure directly from aimdo's side
when needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 23:08:25 +03:00
digital-garbageandClaude Sonnet 5 4960fd2178 test: cover the real-direction cache path round 3 of review found was untested
The previous caching test only exercised the zero-direction torch.zeros()
fallback -- round 1's actual bug lived in the real-direction branch
(direction.to(out.dtype).to(out.device)), which a torch.zeros count can't
see at all. Adds a second test using a deliberately non-matching dtype
(float64 direction against a float32 harness) so .to() is never a
coincidental no-op, then counts .to() calls on the direction tensor itself.

Verified by mutation: temporarily reverted dir_t_cache to round-1's buggy
shape (real-direction path uncached) and confirmed this new test fails
(3 calls instead of 1) while the old zero-direction-only test still passes
-- proving the old test alone would have let that exact regression through.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 19:51:16 +03:00
digital-garbageandClaude Sonnet 5 54706eebe6 test: cover the dir_t_cache reuse round 2 of review flagged as untested
The caching logic itself (a block's second+ call reusing the built
direction/zero tensor instead of reallocating) had no regression test --
only the single-call math was covered. Counts torch.zeros calls across
three calls to the same block; only the first call's mask-cache-miss and
dir_t-cache-miss should allocate, the rest should be pure cache hits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 19:47:03 +03:00
digital-garbage 53d7be9c68 test: cover mixed-mechanism dit-patch leak unwinding + optimized_attention_override slot
Adversarial review of the leak fix found the new tests only exercised REINS
stacked on itself, leaving 6 of 7 install mechanisms and the entire
optimized_attention_override chain (av_decouple/attn_temperature/q_steering)
untested. Add a test stacking REINS -> av_decouple -> q_steering -- different
mechanisms chaining onto each other, the realistic multi-run leak shape -- and
confirm one strip call unwinds both the per-block dit dict and the single-slot
attention override chain.
2026-09-10 23:37:33 +03:00
digital-garbage 310a2be912 fix: H3 dit-patch hooks (REINS/query steering/decouple/temperature/repeat/span/probe) leaked on interrupt
patches_replace["dit"] and optimized_attention_override installs had no tag+strip
protection, unlike the scene-wrapper and block-hook mechanisms fixed for the same bug
class earlier. An interrupted run left these hooks on the shared model; the next run
chained a new layer onto the leaked one instead of starting clean, growing unbounded
across interrupted runs -- the likely cause of the live "potential memory leak...
circular references" warnings and slow full-GC pauses.
2026-09-10 23:28:59 +03:00
digital-garbage 50218f8f67 feat: REINS passive capture, and wire H3 query steering into the Editor
REINS ("h3_repr_steering") only ever captured while its own toggle was on --
turning it off also stopped it from learning, with no way to bank rating
history separately. New h3_repr_steering_passive_capture keeps the existing
unconditional per-block capture running (forcing strength to 0 so nothing is
ever injected) even while the main toggle is off. Exposed a real dormant bug
in REINS' own console print: it claimed "applying learned direction(s)"
whenever a direction existed, regardless of strength -- harmless before
because the toggle and strength widget always moved together, not once
passive capture legitimately runs at strength 0 with real data available.
Fixed to report "capturing only" in that case, mirroring h3_q_steering's own
already-fixed version of the same print.

Also: h3_q_steer_block/h3_q_steer_strength (from the H3 query steering
feature) were never wired into the Movie Editor's Settings UI, only exposed
as raw ComfyUI node widgets -- missing this project's own rule that every
new sampler toggle lands in the Editor in the same change. Added both,
plus the new passive-capture checkbox, to Engine Settings > Chain Guidance
alongside REINS' existing controls.
2026-09-07 04:43:26 +03:00
digital-garbage 676edbfb18 fix: H3 query steering -- NaN strength disagreement, and capture leaking temperature scaling
Two real faults found by a third adversarial review round on 4154746:

- _install_h3_av_decouple's own bail check (`_strength <= 0.0`) disagreed with
  _sample_chunk's install guard (`h3_av_decouple > 0.0`) for a NaN strength:
  every ordered comparison is False for NaN under IEEE 754, so `<= 0.0` let a
  NaN through as "on" while the guard treated the same value as "off" --
  reproducing the exact av_decouple-defeats-q_steering bug the last fix
  closed, just reached through a different value. Both now use the same
  `not (x > 0.0)` predicate so they can't disagree.
- h3_q_steering was installed before h3_explore_temperature, so if both
  target the same block, temperature's Q-scaling reached q_steering's
  capture before it ran -- banking a temperature-distorted "natural Q"
  descriptor, contradicting the mechanism's own documented ordering promise
  (capture reflects the network's natural Q, same claim h3_repr_steering
  makes for hidden state). q_steering is now installed last (outermost),
  after both av_decouple and attn_temperature, so it always sees Q first.
  av_decouple still can't compose with q_steering at all regardless of
  order (unrelated masking issue, already handled by skipping install).

Regression tests for both, plus a NaN case added to h3_av_decouple's own
existing zero-strength no-op test.
2026-09-07 04:26:12 +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 608a97a48d fix: H3 query steering was on by default, and could crash or misreport
Three real faults found by adversarial review of 299941a:

- sample()'s own default for h3_q_steer_block was "0,1" while INPUT_TYPES
  declared "" -- any saved workflow predating this widget omits the key
  entirely, so ComfyUI falls back to sample()'s default, silently turning on
  capture/clone/hook for every H3 generation with a refinement key. Now both
  default to blank, matching every sibling knob in this file.
- The "applying learned direction(s)" console line fired whenever a direction
  was available, not when it was actually applied -- strength=0 still printed
  it, which is the exact silent-success-message trap this project has already
  been burned by. Now split into an "applying" line (strength>0) and a
  "capturing only" line (strength=0).
- A direction banked under a different heads*head_dim layout (model swap on
  the same refinement_key) hit an unguarded .view() and would hard-crash
  generation instead of degrading. Now checked and skipped with a one-time
  notice, same pattern as the existing batch>1 guard.

Added regression tests for all three, plus two coverage-gap tests the review
flagged (kind isolation against REAL persistence, not mocks; two named blocks
steering with independent directions in one run).
2026-09-07 03:55:56 +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-garbageandClaude Sonnet 5 8804744a36 feat: chainable attention overrides + a rating-free H3 attention randomizer
optimized_attention_override was a single dict key that h3_av_decouple claimed
outright -- a second mechanism on the same slot would silently replace it with
no error, the exact bug class dit_patches chaining already guards against
elsewhere. Fixed so any install chains through whatever's already there.

New h3_explore_temperature (+ block spec): flattens attention's own softmax at
named blocks instead of adding ungated latent noise -- variety stays bounded
by the prompt since it can't attend to anything not already in context.
Deliberately NOT wired to REINS or any rating data: neither mechanism's
behavior is understood well enough yet to make coupling them debuggable. A
plain manual dial, same as block repeat. Off by default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 03:00:57 +03:00
digital-garbageandClaude Sonnet 5 a60b0e981f fix: h3_av_decouple was a 0-1 dial over a guessed conversion constant
No way to read H3's actual trained attention-logit scale from this machine
(checkpoint isn't here) -- so normalizing the knob to 0-1 against a hidden
made-up multiplier (12.0) was a second guess wearing a calibrated costume,
and exactly the kind of thing that produces another flat live-tested curve.
The knob is now the raw softmax logit penalty directly (0-50, step 0.5), so
a live sweep searches the actual unit instead of my conversion factor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 02:29:26 +03:00
digital-garbageandClaude Sonnet 5 1e4553e658 fix: H3 attention decoupling was scaling K magnitude, which barely moves softmax
Live 0-1 strength sweep came back flat -- confirmed the mechanism, not just the
value, was wrong. H3's Q/K get RMSNorm'd before attention, so scaling a key
toward zero pushes its logit toward the *mean* of an already near-zero-centered
distribution, not toward excluded. Switched to what actually suppresses a
softmax entry: an additive negative bias on the targeted logits, still passed
through the same per-query-group split (still no S^2 mask, still same total
FLOPs -- the bias is a per-key vector broadcast over the group's queries).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 02:23:30 +03:00
digital-garbageandClaude Sonnet 5 9bbdfc8b4b feat: H3 attention decoupling -- damp video<->audio joint self-attention, no S^2 mask
H3 has no cross-attn to mask (unlike LTXAV's Bounded Attention), so a dense
attention mask would be GB-scale over its ~37k-token packed sequence. Uses
comfy's own optimized_attention_override extension point instead: runs the
same joint attention as separate video-query/audio-query passes against a K
where the other modality's rows are scaled down, at the same total FLOPs as
one pass. New h3_av_decouple sampler input + Engine Settings entry, off by
default (0 installs nothing).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 02:09:15 +03:00
digital-garbage e9174c257c fix: sweep gallery ratings were single-slot, not per-item -- rebuilt on independent slots
The sweep's rating feature was wired against REINS' single 'pending'
capture (one slot per key, overwritten by whatever generates next),
which meant only the MOST RECENT result in a whole batch could ever be
rated -- the opposite of what batch training actually does: rate every
result you generated, independently, in any order.

New h3_repr_capture_slot sampler input (blank by default = unchanged
ordinary one-at-a-time flow) saves a run's capture under an explicit
slot instead of the single pending. New h3_repr_steering.save_capture_slot
/ commit_slot / discard_slot mirror save_pending/commit but keyed, so a
whole batch stays independently rateable the same way batch_training.py's
per-item .cond.pt files already do for value-function training.

New server routes commit/discard a slot directly -- no extra generation
needed, unlike the old 'fire another render with rating attached'
workaround that existed only because of the single-slot limitation.
Every gallery card is now rateable (Like/Dislike/Skip), not just the
last one.
2026-09-05 01:22:23 +03:00
digital-garbage 6c9b0a91aa feat: REINS steers multiple blocks at once, block picker covers all 50
h3_repr_steering_block goes from a fixed combo (18 candidate blocks) to
free text -- a single block, a range, or a comma list, same syntax as
Repeat block(s) (31-40 / 4,5,6). Each named block steers with its OWN
learned direction from its OWN liked/disliked history at that block --
there is no shared 'the direction', just N independent ones applied at
their own positions in the same forward pass. A block short on rated
data at itself just captures, same as any unnamed candidate block.

CANDIDATE_BLOCKS widened from 18 curated blocks to all 50 -- every block
is now both capturable and steerable, not just the ones on the old grid.

Fixed a pre-existing sync gap: h3_block_repeat_last_steps was never added
to settings_card.py's ownership/defaults tables, breaking two golden-file
tests as soon as they ran (test_settings_card_noise.py).
2026-09-05 00:03:56 +03:00
digital-garbage 226e0983f5 feat: h3_block_repeat_last_steps goes negative for 'apply early, then stop'
Same widget, sign now carries direction: positive N = final N steps
(tail, refine only -- what every laststeps test so far has used).
Negative N = first |N| steps instead -- apply while structure is still
forming, then get out of the way and let the untouched rest of the
schedule resolve it, instead of the perturbed step being close to the
last word on the output.

Widget min widened 0 -> -50 (backward compatible: 0 still means every
step, existing positive values unchanged). Sweep panel accepts negative
laststeps in the config line (e.g. 40-41;noseam;1;-2).
2026-09-04 23:01:27 +03:00
digital-garbage f8e777e696 feat: confine H3 block repeat/span loop to the last N schedule steps
Every step was being repeated, including the earliest near-pure-noise ones
where a block is still deciding layout, not detail -- the likely reason
repeat re-rolls the shot instead of sharpening it. New h3_block_repeat_last_steps
(0 = every step, unchanged default) windows the repeat to the tail via the
exact step index off transformer_options[sample_sigmas/sigmas].
2026-09-04 21:34:05 +03:00
digital-garbage e3d63a99a3 feat: span loop — 31,32..40,31,32..40 instead of 31,31,32,32..40,40
A different operation, not a re-spelling. Per-block repeat hands every
block its OWN doubled output, which is a distribution the NEXT block has
never seen, so a ten-block span creates ten seams. A span loop leaves
every block receiving input from its normal predecessor and creates
exactly ONE, at the wrap from last back to first.

That is the obvious candidate explanation for the sweep's central
puzzle: 31,40 and 35,40 survive two extra passes while 31-40 collapses
at the same count. Same total extra block evaluations, so the same cost
— only the seam count differs.

The span's first block does the work (walking
model.model.diffusion_model.blocks, which the hook cannot reach since
original_block closes over one block only) and the rest of the span
becomes a pass-through, because the model's own loop still calls them.

Refuses a non-contiguous selection with a note rather than silently
looping min..max, which would run the blocks deliberately left out.
Under video-only, text and audio rows get the span ONCE — not zero
times, which would skip those blocks entirely for audio and is a
different, worse intervention than "do not repeat them".
2026-09-04 19:56:38 +03:00
digital-garbage b9a570dd0e feat: repeat video rows only, leaving the soundtrack on one pass
Band 31-40 is the best video result in the sweep and invents unprompted
dialogue on every single run of it — a trade that the bisection proved
cannot be dropped by narrowing, because neither half reproduces either
effect.

H3 packs video, text and audio into ONE sequence, so a plain repeat
doubles the block for the audio rows too. This restores text and audio
rows to their single-pass value after the extra passes, keeping the
repeat for video only.

It does NOT undo audio's influence on the second pass — attention had
already mixed the twice-processed audio rows into the video rows before
they were restored — so it fixes the audio that comes OUT, not the video
path. Whether the video gain survives that is the experiment.

Off by default so the measured sweep stays reproducible, and it falls
back to repeating everything when the mask code meets a shape it has
never seen, rather than silently dropping the feature.
2026-09-04 19:45:51 +03:00
digital-garbage a187dc055a feat: detail probe gets its own switch, so it can run without the block probe
They were sharing one toggle on the argument that a second switch for
the same decision is worse UI. That was wrong for a specific reason: the
two probes cost wildly different amounts — the detail probe copies one
latent per run, the block probe gathers ~17k x 5120 rows per block per
step across 18 blocks — and allocator churn on that scale can change
which GPU kernels get selected, so a probe can move the numbers it is
measuring. Answering "is the measurement causing the variation?" needs
one running while the other is off, which a shared toggle made
impossible to perform.

Also: the noise floor is now measured from your own repeat rows (median
of 1 - structure across pairs where nothing changed) instead of graded
against a fixed 0.98 that no real rig reaches. Other rows report their
movement as a multiple of it, so "structure 0.42" reads as "6x your
noise floor" — which is the part that means something.
2026-09-04 18:31:34 +03:00
digital-garbage 24f147418a fix: "prompt/scenes changed" fired on runs with an identical prompt
The conditioning fingerprint was a formatted string, so it was only ever
equal or not — encoder float wobble read exactly like a new prompt, and
every row claimed the prompt changed on four runs where it demonstrably
had not.

It is numbers now (numel, mean, std per entry), compared with a
tolerance, and the row reports the SIZE of the move. That separates
three cases the old label collapsed into one:

  under 0.1%   encoder wobble — not reported at all
  a few %      "conditioning drifted 2.1%" — something rating-driven
               moved it (h3_phrase_emphasis lives on the conditioning
               and updates as ratings land), worth naming but it does
               not invalidate the comparison
  token count  "prompt/scenes changed" — structurally a different
  or entry     generation, and only this still voids the A/B
  count differs
2026-09-04 18:27:09 +03:00
digital-garbage 24f7cec8dd feat: REINS can steer anywhere in 40-49, one block at a time
The influence probe measured the stack as heavily back-loaded — blocks
48 and 49 alone carry about a third of all residual movement, and the
busiest twelve are almost all in the 40s — while REINS defaults to block
25 and the picker stopped at 40, so it could not reach where the model
does most of its work.

CANDIDATE_BLOCKS now covers 40-49 individually (the quiet early half
stays on the coarse grid). Capture has to cover them too: direction()
reads descriptors at the exact block being steered, so a picker entry
outside the capture set would say "not enough data yet" forever. A test
pins that the two sets cannot drift apart — the same silent-substitution
class as the rating combo that discarded every digit.

Node and Editor choice lists verified byte-identical: a mismatch there
makes ComfyUI's widget validation quietly swap in the default.
2026-09-04 18:23:58 +03:00
digital-garbage 8a670fdf60 fix: a prompt change was reported as "nothing changed"
The run fingerprint captured only scalar arguments, and the prompt
arrives as conditioning tensors — so two runs of completely different
text compared as a repeatability check, and the resulting structure drop
(0.44-0.93) read as "generation is not reproducible here" when one row
in the same batch showed a bit-identical pair (structure 1.000, detail
x1.000, edge +0.000), proving it demonstrably is.

Fingerprint now includes a conditioning stand-in: per-entry shape + mean
+ std, which differs between any two real prompts and costs two
reductions on a tensor already resident. The row says "prompt/scenes
changed" and refuses to call the pair a noise-floor reading.
2026-09-04 18:18:41 +03:00
digital-garbage be5579f1f9 fix: rank blocks by share of movement, not by a ratio depth decides
Live data showed block 1 at 6.01 and block 0 at 1.83 against ~0.2-0.35
everywhere else, with block 1's liked/disliked gap (+5.82) as large as
its whole measurement. That is not "the first two blocks do 30x the
work" — ||f_i||/||x_i|| divides by a residual stream that GROWS with
depth, so early blocks score high because the denominator is barely
formed. Ranking on it just sorts the earliest blocks to the top, and a
high-variance early block fakes a large taste correlation at low n.

The hook now stores both norms instead of their ratio, and profile()
adds `share` — each block's raw ||f_i|| over every block's, one
denominator for the whole stack. The panel ranks and displays that
("2.3% of the movement"). The ratio is kept, since it is still the right
answer to "how much did this block move the stream relative to what the
stream was".

Checked against data shaped like the report: ratio ranks 0,1,2 (all
artifact), share ranks the blocks that actually move more.
2026-09-04 18:10:55 +03:00
digital-garbage b6841cbbd7 fix: the detail row now says what the A/B actually was
Every row read "no repeat → no repeat" because the label only ever
tracked one knob — the block-repeat spec — so a comparison where
anything else changed, or nothing did, described itself as unchanged and
told you nothing about what it compared.

sample() now snapshots its scalar arguments (locals() at the top IS the
argument list, so no name list to go stale), and each row keeps the DIFF
against the previous run. The row titles itself:

  h3_block_repeat: — → 40      sharper — +204% detail, on existing edges
  nothing changed              stable — two identical runs match, so a
                               real A/B can be trusted
  seed 111 → 222               different seed — not comparable

The "nothing changed" case is the valuable one and was previously
unreachable: two identical runs measure how much generation varies on
its own. If that comes back unstable, no A/B on the setup means
anything, and the row says so instead of letting every later comparison
quietly inherit the noise.
2026-09-04 18:07:08 +03:00
digital-garbage 7125c1ba4d fix: block-influence probe read "before" AFTER running the block
A block that writes its residual in place leaves args["img"] already
holding the post-block values, so every delta came out exactly zero —
and a zero delta gives a zero cosine, so magnitude AND novelty both read
0.0000 / +0.00 on every block. That looks like "this model has no block
structure", which is a finding, when it actually meant the probe
measured nothing. The exact silent-no-op class this probe exists to
catch, shipped inside the probe.

Snapshot the sampled rows before calling the block. Advanced indexing
copies, so the snapshot is not a view onto src.

Regression test pins it: a block mutating img in place must still report
|delta| = 2x|before|. Verified it fails with 0.0 against the old code.

Also: the readout used toFixed(4), which hides exactly the range these
values live in — the intervention map measured block activity varying in
the 4th decimal, so 0.0000 was ambiguous between "tiny" and "nothing".
Now toPrecision(3).

Detail probe: records the seed with each run, so a low `structure` can
say which of its two causes applies — different seeds (two separate
generations, never an A/B; a reference pins the subject, not the sample)
or the same seed whose shot the change actually moved, which is a
result. The numbers cannot separate those; the seed can.
2026-09-04 18:01:07 +03:00
digital-garbage b1f80ef45f feat: block repeat + the detail measurement that can falsify it
h3_block_repeat runs the named block(s) on their own output before
passing it on — a second pass that never leaves latent space. To first
order this doubles that block's contribution; the nonlinear part is a
genuine second refinement of its own result. ~2% of a step per repeat.

detail_probe scores whether that (or anything else) actually sharpened
the picture, by comparing a run against the previous one on three
numbers:
  detail        high-frequency energy — did fine structure increase
  structure     low-frequency survival — same shot, or a different one
  edge-aligned  did the EXTRA detail land on existing edges, or spread

The third is the one that earns its place: noise is high-frequency too,
so `detail` alone cannot tell sharpening from grain — and grain is
exactly what an out-of-distribution stream would produce. Verified on
video-latent-shaped data: unsharp-mask reads detail x3.04 / edge +0.885
("sharper"), uniform noise reads detail x1.12 / edge -0.248 ("grain").
Both raised detail; only edge-alignment separated them.

Measured in latent space deliberately — the tensor is already in hand at
the call site DynaShift banks from, so no VAE decode and no new
plumbing. A real approximation, valid for the A/B it is for (same seed,
identical decoder both sides) and documented as ratio-only.

Reuses block_influence's collection switch rather than adding a second
research toggle, and pairs CONSECUTIVE runs, which is the workflow
anyway: generate with it off, generate with it on, read the row.

Also scores second_pass sharpen, quality_sharpness and segmented
detailing — every mechanism here that claims detail and is currently
judged by eye.
2026-09-04 17:51:01 +03:00
digital-garbage e987f6036f feat: block probe also measures NOVELTY — what each block adds that the
previous one didn't

Magnitude alone cannot distinguish a block contributing something nothing
before it contributed from a block pushing harder in the direction the
last one already went. Records cos(f_i, f_i-1) on the same strided video
rows: ~1 means it amplifies its predecessor (little new per block), ~0
means orthogonal (a genuinely new component), negative means it partly
UNDOES the previous block — which real stacks do, and which would mean
the effective work is far smaller than the sum of the magnitudes.

This is the number that speaks to whether 50 blocks are doing 50
different things or one thing 50 times.

Step boundaries are respected: a block index that does not exceed the
last one seen starts a new step, so block 0 is never compared against
block 49 of the previous step — that pairs the end of one denoise step
with the start of the next, which is not a depth relationship.

Reads out in Refinement & Taste beside flatness, with a plain-language
verdict and a per-block column. Verified live on seeded data: amplifying
(+0.81), undoing (-0.49) and orthogonal (+0.06) regimes all round-trip
through the API and render. Seed data cleared.
2026-09-04 17:37:26 +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