How much of a reference image actually survives into a generated one — measured, not asserted.
gate 6/6 GREEN live MCP tool sovereign NishiLang 2026-07-25
We already measured that natural language is a lossy codec for image generation: semantics survive at roughly 85%, geometry at roughly 50%, and identity at 0%. That is why the structure channels (reference-conditioned generation) exist at all.
But an unmeasured channel is an unproven channel. Without a ruler, “img2img preserved the composition” is an opinion. This organ turns it into a number, on the same instrument for every side.
A generator can hold global layout while destroying local structure, or hold local structure while shifting every tone. Those are different failures with different fixes, so they get different numbers and the headline reports the minimum. One blended number is not a measurement.
| Channel | Method | What it catches |
|---|---|---|
| structure | SSIM (Wang 2004) | local luminance / contrast / covariance change |
| perceptual | dHash Hamming (Krawetz 2011) | gross layout drift; invariant to uniform brightness and rescale |
Agreement between two independent descriptors is evidence. A single descriptor is a target waiting to be gamed.
A wardrobe edit (yellow sundress → red evening dress) applied to a locked scene at increasing denoise strength, each output scored against its own source image. Same seed throughout.
| denoise | structure | perceptual | retention (MIN) | |
|---|---|---|---|---|
| 0.35 | 878 | 859 | 859 | |
| 0.55 | 647 | 796 | 647 | |
| 0.75 | 359 | 593 | 359 | |
| unrelated control | 284 | 500 | 284 |
Monotonic, and the unrelated control sits below the whole curve — so the instrument discriminates rather than just producing numbers.
The non-obvious result. Changing only the sampler step count (8→20) on an otherwise identical prompt and seed scored 526. A step-count change disturbs the image more than a moderate reference-preserving edit does (859).
Consequence for any consistency work: pin sampling steps as hard as you pin the seed. A pipeline that varies steps between runs is re-rolling the subject.
A ruler that has never been seen to say no is not a ruler. Every tooth below runs on the NAS, against the same code that ships.
| # | Tooth | Result |
|---|---|---|
| T1 | Identity — an image against itself | 1000 / 1000 |
| T2 | Discrimination — a mirrored copy (identical histogram, different structure) | 446 structure — rejected |
| T3 | Scale invariance — 128×128 vs its own 64×64 downscale | 992 |
| T4 | Monotonicity — corruption 10% / 30% / 60% | 465 → 208 → 94 |
| T5 | MIN headline — a strong channel may never flatter a weak one | (900,300)→300, never the mean |
| T6 | Fail-closed — an unreadable image refuses instead of scoring | refused |
T6 exists because of a real trap this ecosystem already fell into: a failing producer once wrote a perfectly valid image of an empty render, which would have satisfied an evidence check that only asked “does the file exist?”. Two failed decodes must never become two zeroed planes that score a perfect 1000 against each other.
Cross-build proof. The gate produces byte-identical verdicts under two independent toolchains — the local sovereign compiler and the on-NAS build — on every tooth. Two implementations agreeing is evidence; one implementation agreeing with itself is not.
nx_refbench compare <ref.png> <gen.png> nx_refbench selftest nx_refbench_gate nx_converge plan <target> <tol> nx_converge next <target> <tol> <lo> <hi> <probe> <measured> <steps> nx_converge simulate <target> <tol> nx_converge_gate
next is deliberately stateless — the search bracket travels in the
arguments, so an agent or workflow drives one render at a time and the entire search is replayable from
its own transcript. State that lives in a daemon cannot be replayed.
Live over MCP. Composes only already-proven organs — the PNG decoder, the canonical image type, SSIM, and the perceptual hash (whose downscale kernel is reused as the common comparison plane, so a 2550×3300 reference and a 768×1024 render are compared at one scale by one kernel). No new ranking or resize maths were written.
nx_convergeA ruler is only half of it. nx_converge turns "guess a denoise, render, squint, guess again" into a controller: you state the retention you want, and it bisects to it.
This is exact rather than heuristic. Retention falls monotonically as denoise rises — proven, not assumed, by a dedicated tooth — and bisection on a monotonic function is guaranteed to converge in a known number of steps. The render budget is knowable before the first render, which matters when each one costs 30–60 seconds of GPU.
| step | denoise | measured retention | time |
|---|---|---|---|
| 1 | 500 | 666 | 51s |
| 2 | 250 | 882 | 30s |
| 3 | 375 | 798 | 39s |
| 4 | 312 | 845 | 36s |
Target 850, tolerance 15, converged in 4 renders on real hardware — sovereign controller decides, GPU renders, sovereign ruler measures, repeat. The gate proves it lands across the whole range in at most 5 renders, refuses impossible targets for zero renders, exits after one render when already on target, and replays identically.
And here is what that experiment actually taught us — the part worth publishing. The loop converged beautifully. Then we looked at the image: the requested emerald-green dress was still yellow. Not at 845 retention, and not at 666 either. The edit never landed at any denoise we tested.
The controller was right. The ruler was right. The objective was wrong. Retention measures preservation, so optimising it alone walks you toward changing nothing at all — a textbook case of the metric quietly becoming the target.
The fix is not a better denoise. It is a second judge that asks whether the requested change actually happened, optimised jointly with retention, plus a real edit mechanism (a garment-region mask, which this engine already accepts) rather than relying on denoise strength to do editing it was never able to do. That is the next rung, and it exists because we looked instead of trusting a green number.
| Gap | State |
|---|---|
| Geometry channel (ControlNet) | architecturally incompatible — the ControlNet block builds only SD1/SD2/SDXL/SVD UNets, and this model has no control input at all |
| Identity channel (PhotoMaker class) | architecturally incompatible — hardcoded to SDXL |
| Native reference channel | present and invoked, but crashes: the positional encoding is not extended for the concatenated reference latents |
| Identity retention metric | this ruler scores whole frames; face-region identity is not yet isolated |
| Edit-compliance judge | missing — and its absence is why retention-only convergence lands on "change nothing" |
| Masked-inpaint edit channel | the engine accepts a mask; not yet wired into the loop |
Correction — an earlier version of this page said the opposite, and it was wrong. We published that both structure channels were “gated on model acquisition, not on code,” and called that the cheapest large unlock available. Reading the engine source refutes it: the ControlNet block is built only for SD1/SD2/SDXL/SVD UNets, the identity encoder is hardcoded to SDXL, and this model routes to a separate denoiser that accepts neither. Downloading those weights would have been wasted gigabytes. They are incompatible, not missing.
What does exist is the model's own native reference channel: the denoiser genuinely accepts reference latents, and the API already carries them end to end — nothing to download. Supplying a reference crashed the process on an internal assertion, because the positional encoding was never extended to cover the concatenated reference.
Root cause found, and it was a stub. The positional-id generator accepted the
reference latents as a parameter, threaded them through — and then discarded them under a comment
reading // ignore ref_latents for now. So the reference tokens were given no positions at all,
the encoding came out shorter than the sequence indexing it, and the assertion fired. The repair appends
their ids using the very same helper the other model family already uses, so no new positional maths was
introduced.
Same seed, same prompt (“a woman on a city street at night”), full denoise so the input image contributes nothing — the only variable is whether the reference is supplied.
| reference | retention vs the reference | what you see |
|---|---|---|
| off | 24 | prompt honoured — and a completely different person |
| on | 507 | our subject, unmistakably, at the same seed |
A 21× separation on an independent ruler. That 24 is precisely the “text alone carries essentially no identity” floor measured earlier; 507 is the channel doing the work text cannot. The unlock everyone assumed was an acquisition programme was a ten-line repair to an abandoned stub.
The section below was written when this channel was all-or-nothing. It has since been built and the limit removed — kept here because the path to the fix is the useful part.
| reference weight | retention vs reference | what you see |
|---|---|---|
| 1.0 | 516 | reference dominates; prompt has no effect |
| 0.6 | 464 | blending begins |
| 0.3 | 330 | identity preserved, and the scene moves to the requested night street |
Monotonic, and visually confirmed. The control scales the reference token embeddings after the embedder — the one place attenuation is meaningful — so 1.0 reproduces the old behaviour exactly and lower values let the prompt reassert.
And a correction to the section below. Our first sweep of the new control came back completely flat, and we nearly concluded the model change had failed. The real cause was that this API path never parsed the extra-argument block at all — so the value never reached the model, and worse, the unparsed text stayed inside the prompt and changed the conditioning by itself.
That also invalidates the refutation written below: the “504 versus 508, therefore noise” result was measuring prompt text, not the knob. A control tested through a channel that does not carry it has not been tested. Verify the parameter arrives before declaring it ineffective.
Identity transfer is real. Being able to aim it is not — yet. We tested both controls that already exist, and measured both to be ineffective:
| knob | measured effect on retention | verdict |
|---|---|---|
| reference index offset (on / off) | 504 vs 508 | noise — it re-indexes positions, it does not weight |
| denoise 0.4 / 0.6 / 0.8 / 1.0 | 576 / 569 / 526 / 516 | a 60-point band — denoise barely matters once a reference is attached |
Before the repair, denoise swung retention from 859 to 359. With a reference attached it moves almost nothing, because the reference route genuinely overrides the input-image route. At full denoise with a “city street at night, dark winter coat” prompt, the output is the reference reproduced — same garden, same dress. The prompt has no effect at all.
So the accurate claim is narrower than it first looked, and worth stating plainly: the identity pathway is proven and carries identity strongly (507–576 with a reference against 24 without). But it currently behaves as a copy channel, not a controllable identity conditioner — you cannot yet place this subject in a new scene.
A genuine strength control means scaling the reference token embeddings inside the model. Scaling the latent beforehand is not attenuation: it yields a different image rather than a weaker reference. That is deeper engine surgery, and it is the honest next rung — recorded here rather than left implied by a headline number.