Commit Graph
1589 Commits
Author SHA1 Message Date
digital-garbage 175b08ff32 fix: panel threw on every detail row (temporal dead zone)
`const moved` sat below the repeatability branch that uses it, so that
branch hit "can't access lexical declaration 'moved' before
initialization" the moment a row rendered. Valid syntax, so node --check
passed it — the failure only exists at runtime.

Declarations moved to the top of dpVerdict, and the panel logic now has
a test that RUNS every branch instead of parsing it. That is the actual
lesson: this file had no runtime coverage at all, and a syntax check
cannot see a TDZ.

Also: the block panel showed a cumulative count beside a green dot while
recording was off, which reads as "still recording". It now says
"Recorded earlier (not recording now)" and drops the dot. Turning
recording off has never cleared stored profiles and still doesn't — the
count is history.
2026-09-04 18:37:40 +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 8e31dcbff6 fix: report when DynaShift's positive pull is silently inactive
The status line at generation start only counted bank sizes, not
whether entries actually match this run's latent resolution -- so
"positive pull active (N liked)" could be true by that rough count
while the real per-resolution check inside the wrapper found nothing
usable, with no way to tell from the log. Negatives already had this
warning; positives didn't. Now both report a resolution mismatch.
2026-09-03 23:56:43 +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 a239cdb1b8 fix: h3_repr_steering_block widget was inserted mid-list, breaking
positional reference-workflow mapping

builder.py's extract_widgets maps a reference workflow's stored
widgets_values onto this node's CURRENT INPUT_TYPES order, positionally.
Inserting a new widget between h3_repr_steering_strength and
alg_guide_blur_strength shifted every widget declared after it by one
slot for any project that hadn't explicitly overridden that field in
sampler_inputs -- silently feeding it the previous field's stale value.
Moved to the end of the widget list (append-only, per the existing note
near debug_log) so nothing downstream is displaced.
2026-09-03 22:52:31 +03:00
digital-garbage 4e08348574 feat: extend REINS candidate/steerable blocks down to 1 and 5-15
CANDIDATE_BLOCKS now covers the whole depth range (1-45), and the
block-choice combo (node + Editor) offers 1/5/10/15 alongside the
existing 20-40 -- test-only, for probing the shallow end where the
anchor/reference rows haven't been attended to yet.
2026-09-03 22:37:59 +03:00
digital-garbage 978e762e90 feat: expose REINS block-choice in the Editor's Engine Settings
Added a dropdown next to Representation steering strength, not just on
the raw node -- same as every other sampler toggle this project has
shipped.
2026-09-03 22:25:55 +03:00
digital-garbage ad95c03f6e feat: pick which block REINS steers (test-only)
New h3_repr_steering_block combo (20/25/30/35/40, default 25) lets a run
inject the learned direction at a different candidate block instead of
always DEFAULT_BLOCK. Capture and block_sweep across all candidate blocks
are unaffected.
2026-09-03 22:22:57 +03:00
digital-garbage ad9a8f1c5e fix: block 25's captured descriptor was taken AFTER injection, not before
Confirmed by reading the hook directly: capture() ran on `out` after
the steering push was already added, and injection only ever happens
for block == DEFAULT_BLOCK -- so block 25's own logged descriptor
depended on whatever strength was dialed in at generation time, while
every other candidate block (never injected into) stayed clean. That
made block 25's block_sweep entry incomparable across different
strength settings and incomparable to the other 8 blocks, which is
exactly the inconsistency observed live (block 25 significant at
strength 0.1, not at 0.05, on data collected under each).

capture() now runs before the injection is added, so the logged
descriptor always reflects the network's natural behavior at that
block regardless of steering strength -- generation still receives
the injected push, only the log changed.
2026-09-03 21:30:38 +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 4d92451b00 feat: add a rollback button for the last Update/Switch branch
Reads git's own reflog (HEAD@{1}) rather than a separate state file
-- pull()/checkout() already move HEAD, so git already remembers what
it was before, and there's nothing new to keep in sync. Refuses on a
dirty tree, same as pull()/checkout(). Does not reinstall
requirements for the target commit -- reversing a pip install
automatically risks breaking other already-installed packages, out of
scope here; surfaced as a warning in the confirm dialog instead.

Welcome screen / Settings maintenance row goes from 3 buttons to 4;
only enabled when there's actually something to undo.
2026-09-03 21:21:35 +03:00
digital-garbage 7d2a33f92b perf: vectorize separation_statistic/permutation_test's O(n^2) inner loop
Measured, not assumed: 9 blocks x 2000 trials at n=30 took 8.16s
before this (pure-Python nested loops, GIL-held throughout -- moving
it off the event loop via asyncio.to_thread does NOT mean it stops
costing real wall-clock time, it just stops blocking OTHER requests
while it costs that time). The O(n^2) between/within split is now a
single tensor broadcast per call, and the biggest remaining cost
(permutation_test recomputing the full _unit()+matmul on every one of
2000 trials, when only the shuffled labels change) is hoisted out of
the loop. Same 9x2000 sweep: 0.76s, 10.7x faster.

All 48 existing trajectory_probe tests pass unchanged -- same exact
p-values, same edge cases -- confirming this is a pure speed fix, not
a behavior change. Benefits both trajectory_probe's own "Read" button
and h3_repr_steering's block_sweep, which share this code.
2026-09-03 20:53:29 +03:00
digital-garbage fdc75d1aed fix: block sweep was running on every Settings panel open, stalling the server
block_sweep() is a 9-block permutation test (2000 trials each by
default) -- heavy enough on CPU that running it synchronously inside
the status route (which fires on every panel mount) blocked the same
event loop ComfyUI's own server runs on. Confirmed live: opening
Settings > Refinement & Taste froze an in-progress generation.

Split the same way trajectory_probe already is: status stays cheap
(no sweep), the sweep moves to its own POST route, run off the event
loop via asyncio.to_thread, triggered only by an explicit "Read"
button instead of automatically on mount.
2026-09-03 20:41:41 +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 19ce43bfb8 feat: H3 picker goes binary (Liked/Disliked); REINS gets its own capture pause
Picker: h3_repr_steering only ever reads the reward's SIGN now (see
the binary-direction commit), so a 1-10 scale was granularity nothing
downstream uses. Down to two buttons -- reuses the scale's own "10"
and "1" endpoints, zero backend change.

REINS capture toggle: independent of the sampler's h3_repr_steering
widget, which controls whether the learned direction gets APPLIED --
this new per-key flag controls whether it keeps RECORDING at all, so
data collection can be paused without touching the steering setting
or discarding what's already captured. Settings > Refinement & Taste
gets the toggle; save_pending() is the single gate.
2026-09-03 20:00:01 +03:00
digital-garbage cbc0c322a9 fix: REINS direction reverts to binary (sign only), not weighted by magnitude
Weighting by rating magnitude (a 9/10 pulling harder than a 6/10) is
the better estimator in the limit, but needs enough data for a
weakly-rated row's noise to average out. At the handful of ratings a
key actually accumulates, a mediocre "6" just gets a vote and dilutes
the direction with an ambiguous case instead of being excluded by it.
Binary spends what little data exists on the clearest examples only.

block_sweep(), multi-block capture, and prompt-hash grouping are
unaffected -- only direction()'s own math changed.
2026-09-03 19:54:49 +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 eb0c76ef9a feat: lower REINS' activation floor from 3+3 to 2+2 rated generations 2026-09-03 19:06:04 +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 3a62759be9 feat: a way to throw the learned state away and start clean
Settings ▸ Learning ▸ "Start a fresh measurement". Deleting the refinement key
was not enough: the probe log lives outside the <key>.* sidecar convention on
purpose, so it survives a key clear, and the per-bucket heads are REBUILT from
it on every rating — the next rating restored them from runs still on disk.

So the clear takes both, and leaves the switch alone: "throw away what was
measured" and "stop measuring" are different requests.

Verified against a running ComfyUI: 12 runs and the heads file removed, count
back to 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 14:47:26 +03:00
digital-garbageandClaude Opus 5 787e379ffc fix: trajectory guidance never trained — the heads were empty every run
ComfyUI executes a node under torch.inference_mode(), so a descriptor read out of
the probe log becomes an inference tensor and autograd refuses it:

  Trajectory probe intake failed: Inference tensors do not track version counter.

Every head stayed empty and the run reported "nothing learned yet" on every
generation — so the feature was OFF while looking merely untrained, which is the
one failure invisible from the outside: "not steering" is also what correct
behaviour looks like before enough ratings. Seen on a live rental with 73 runs
logged and zero of them trained.

The output value function's trainer already carries this guard; this one did not.

Also: the bucket-isolation test was measuring TRAINING accuracy, where a
few-hundred-parameter MLP fits 20 noise vectors perfectly and every bucket scores
1.0. It now holds rows back, which is the only version that can tell a head that
learned from one that memorised.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 14:41:00 +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 ff2e1bf046 fix: compare what differs between runs, not what they share
Runs of one prompt share most of their content: on a real 21-run session, 86% of
each descriptor was the part every run had. Comparing raw vectors measured that
shared part, so every distance came out near zero and near equal — mean cosine
0.97, and no separation at ANY bucket including the late ones where steering
demonstrably works.

Centring first flips the same data from -0.002 (backwards) to +0.03 and p 0.83
to 0.11, consistent across all four buckets. Not significant at 5 good / 16 bad,
but pointing the right way and now limited by sample size rather than by the
statistic.

The descriptor was not at fault. adaptive_avg_pool1d over the flat latent keeps
512 REGIONAL means, not one global mean, and it distinguishes clips fine.

Also: the prompt id is quantised before hashing. Raw bytes answered "are these
bit-identical", so any value crossing a rounding boundary changed the id — one
prompt produced 20 distinct ids across 21 runs. Stable now for jitter of the
size a GPU produces; no exact hash of floats can do better.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 13:44:48 +03:00
digital-garbageandClaude Opus 5 349cf75859 feat: carry the trajectory measurement between machines
Settings ▸ Learning gains Download and Load. A rental is replaced whenever
something breaks, refinements/ is not in git, and the reading needs ~16 rated
runs — so without a way off the box the count restarts on every instance and
the answer is never reached. 16 runs is ~85 KB.

Importing the same export twice adds nothing: the permutation test reads n
directly, so duplicated runs would report a separation as far more significant
than the evidence supports.

An uploaded .pt is a pickle, so the restore parses it with weights_only — every
field in a run is a tensor or a primitive, and anything else is refused rather
than executed. Nothing but numbers, a rating key, a timestamp and a one-way
prompt hash is stored; no prompt text has ever been written to these files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 13:06:33 +03:00
digital-garbageandClaude Opus 5 818ed26c49 feat: trajectory probe lives in Settings, not the terminal
Settings ▸ Learning ▸ Trajectory probe: a switch to record, the run count,
and a Read button that gives the answer in plain language. The switch is the
same environment variable the sampler reads and the editor sets it in the
running process, so it takes effect on the next generation with no restart —
and it is written to disk, so a restarted box comes back recording instead of
having quietly stopped.

tools/trajectory_separation.py stays as the terminal door for a headless box.

Verified against a running ComfyUI: toggle both ways, persistence across a
real restart, and the reading recovering a planted early signal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 15:55:37 +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-garbage deeb4c0b74 docs: announce FunPack v5 "Cunning Cranberry" 2026-08-27 16:26:00 +03:00
digital-garbage e35bae00a5 Merge v4 into main - Release 4.0.0 "Blinding Blackout" 2026-08-27 14:48:14 +03:00
digital-garbageandClaude Opus 5 3b7f21bf30 Merge main into v4
main carried two commits v4 branched before, including the README cleanup.
README resolved to v4's, which is main's own text with the "v4 in progress"
section replaced by What's new. Nothing else differed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:47:22 +03:00
digital-garbageandClaude Opus 5 807c1b9999 docs: drop the mid-scene guide from the sampler reference
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:44:23 +03:00