Commit Graph
336 Commits
Author SHA1 Message Date
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
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 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 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 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 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-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 0158cd0eb9 maint: take segmented detailing out of the Editor, keep the code
Never validated on a real run, so 4.0.0 does not offer it. The widgets stay
for a raw ComfyUI graph and the module is marked FOR FUTURE REFERENCE — the
CLIPSeg tube, the upsampler pass and the feathered paste-back are the hard
parts and are worth having if it is picked up again.

The card still hides its settings when it is off, so a graph that does tick it
still reads correctly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:38:56 +03:00
digital-garbageandClaude Opus 5 987beef9d9 maint: remove the H3 audio clock
Did not work. The widget, the per-step correction, the denoiser proxy for
samplers whose loop we do not own, the sampler-suitability tables, and its
test file.

The step helper was identity without a clock, so every call site in the
distilled-flow and hybrid loops becomes the value it was passing through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:36:03 +03:00
digital-garbageandClaude Opus 5 7537ec0bef maint: remove the mid-scene guide
Not used on H3, not used on LTX any more. Two widgets, the append method, the
Editor's continuity controls for it, and the auto-enable that turned it on for
every multi-scene carry chain.

JoyAI memory and carried i2v guides keep the guide-attention machinery they
share with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:32:20 +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 8374838e46 maint: remove the reference-weight dial (h3_prompt_time)
Seven rental runs across its whole range: below 0.5 the picture breaks up,
above 0.5 it reproduces the reference outright, and nothing in between is
worth having. It never did the job it was built for either.

Kept: the final-layer video scale it shipped alongside, which does work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:23:57 +03:00
digital-garbageandClaude Opus 5 511713593c maint: remove the plateau step-cache
Measured on the rental and it never worked as intended. Two widgets, the
wrapper, the context-windows exclusion it needed, and its two tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:21:58 +03:00
digital-garbageandClaude Opus 5 663eae2c3a feat(card): drop dead keys and defaults from the Studio block too
On the card from the rental: Chain Sampler 35 rows -> 4, Studio 13 -> 6,
Studio inputs 2 -> 0.

Three additions. A key the node does not DECLARE is now dropped rather than
kept: ComfyUI builds a node's arguments from its own INPUT_TYPES, so
`block_steer` (removed from this pack entirely) and a `steps` key the Chain
Sampler has never had reached nothing, while printing beside live settings as
if they had. An empty value decided nothing either. And the studio_settings
refiner block is filtered against the Editor's own defaults — it has no node
to ask, so an unrecognised key there is still kept: unjudgeable is not inert.

identity_projector was printing a model filename under a disabled feature. It
is rendered by hand in the Editor so it carries no dependsOn, hence
_OWNED_EXTRA, kept apart so the generated half stays generated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 04:41:38 +03:00
digital-garbageandClaude Opus 5 138e13d901 feat(card): print what produced the video, not every widget that exists
Two rules: skip a value equal to the widget's own default, and skip a value
whose feature is switched off. A region-sharpening prompt printed under a
disabled sharpener read as if it had run, and enough of that buries the few
values that did decide the render. On a realistic project this cuts the Chain
Sampler section from 8 rows to 4.

Defaults are read off the node's own INPUT_TYPES rather than copied here.
Ownership mirrors the Editor's dependsOn wiring, and a test regenerates the
table from engine_settings.js so the two cannot drift. Owner chains are walked
to the end — v2a_grad_scale belongs to joyai_audio_memory, which belongs to
joyai_memory. A section left with nothing to say is dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 04:34:22 +03:00
digital-garbageandClaude Opus 5 cb5910568e feat(h3): reference weight applies only after the shot has been composed
Measured: 0.4 loose and inventive, 0.6-0.75 several versions of the shot
attempted at once with the strongest winning, 0.75 and up simply the
reference. No usable band anywhere in the range.

The timestep is how the model knows what stage everything is at, so a prompt
that disagrees with the picture about the stage costs it the ability to settle
on one reading — which is what "multiple variants in one video" is. Structure
is chosen early, so the edit now waits until it has been: ramped in through
_make_steer_ramp, the same late-step gate the rating-driven wrappers share,
interpolating from the prompt's own row so a closed gate is byte-identical to
not being installed.

Third hypothesis for this dial. If it still just reproduces the reference, the
feature does not earn its place and comes out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:55:24 +03:00
digital-garbageandClaude Opus 5 217882e695 docs(h3): "Prompt is settled" is a reference-weight dial — relabel it as one
Measured, after the label fix: at 1.0 it still reproduces the reference
almost exactly, so the <Picture N> label was never the cause. In H3
"finished" is what marks a row as reference material — content that was given
and should be reproduced. Marking the prompt rows that way moves them into
that category, and the reference takes over the frame.

Measured range: 1.0 copies the reference; ~0.4 follows the prompt but invents
things in neither; below 0.3 the picture breaks up. It does not improve
prompt adherence, which is what it was built for.

The widget NAME stays h3_prompt_time — it is in saved projects and saved
workflows, and renaming it would drop the setting from both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:49:15 +03:00
digital-garbageandClaude Opus 5 450be1c232 fix(h3): picture detail ran backwards — H3's final-layer scale is negative
Measured on a rental: 0.8 gave more contrast and 1.2 less, and 1.15 was
nearly inert.

The layer computes norm(x) * (1 + scale) + shift, and the first build
multiplied `scale` itself. H3's scale is negative, so raising the dial shrank
(1 + scale) and the control inverted. Scaling the whole (1 + scale) term
instead is monotone in magnitude whichever sign a checkpoint carries, so
"above 1.0 is more" holds by construction rather than by luck. shift is still
left alone — it is the bias.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:38:46 +03:00
digital-garbageandClaude Opus 5 1652b7197a fix(h3): "Prompt is settled" was settling the reference label, not the prompt
Measured on a rental at 0.9-1.0: it reproduced the reference image almost
untouched in the frame, overriding a prompt that said in words the reference
was not where the scene begins.

An r2v conditioning is laid out `<Picture n>: ` label / vision block / prompt,
and the LABEL is text — the vision block is not. The first version re-timed by
modality tag, so it hit the label too. That label already reads as "compose
like this"; made maximally authoritative, the model composed like it.

The prompt is the LAST text run, and nothing outside the text span carries the
text tag, so that identifies it with no help from the conditioning. Row ranges
live only in mod_segments, which AdalnProj never sees — so this moves onto
ComfyUI's own per-block replace hook, which is handed them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:36:50 +03:00
digital-garbageandClaude Opus 5 f87356bc9b fix(editor): a stray letter could split a clip from a window you were typing in
Reported from a rental: typing in the Composer, focus was lost, typing
continued, and a clip was cut in half by an `s`. Focus loss lands on <body>,
which the handler read as "nothing focused, so this is for the timeline".

While a writing surface is open — the Composer, any floating window — letters
belong to it whether or not it holds focus. And with "Show timeline on hover"
on and the timeline collapsed, clip edits are refused outright: a selection
nobody can see gives no sign anything happened. Transport keys stay live in
that case; the player is on screen either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:32:17 +03:00
digital-garbageandClaude Opus 5 d771ac08b9 fix(editor): the H3 render dials could not be set at all
The key-scoping strip lived in Project.from_dict, which parses the browser's
SAVE payload as well as the file on disk. So the Editor sent a value, got a
project back without it, and redrew the default — reported from the rental as
the mode switching itself back to learned. Every one of these dials was
unsettable, not just the mode.

The strip now runs only when a project is read from disk. h3_gain_mode and
h3_prompt_time leave the set entirely — a mode is a choice, and prompt_time is
never learned. Manual mode strips nothing: those values are hand-typed, and
there is no learned state to reset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:21:35 +03:00
digital-garbageandClaude Opus 5 d2242b5794 feat(loaders): say it when H3 is loaded at a precision that breaks it
fp16 and fp8_e4m3fn_fast both wreck H3's audio before anything looks wrong,
and the loader's own notes go into a status string the Editor never renders —
so the only symptom was a bad soundtrack. One console line, once per run, on
H3 only. Names the fix in the first sentence, which is the half that survives
the console trim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:16:43 +03:00
digital-garbageandClaude Opus 5 a5f7679321 fix(h3): the prompt dial no longer costs you the rating loop
h3_prompt_time is not one of the learned strengths, but it was gated behind
h3_gain_mode=manual like the ones that are — so using it meant switching
rating-learned gains off. It now reads its widget in both modes, and shows in
both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 00:12:55 +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