nx_refbench — the reference-retention ruler

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

Why this exists

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.

Two channels, headline = the weakest

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.

ChannelMethodWhat it catches
structureSSIM (Wang 2004)local luminance / contrast / covariance change
perceptualdHash 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.

Measured: the structure-lock retention curve

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.

denoisestructureperceptualretention (MIN)
0.35878859859
0.55647796647
0.75359593359
unrelated control284500284

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.

Why you can trust the numbers: the gate

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.

#ToothResult
T1Identity — an image against itself1000 / 1000
T2Discrimination — a mirrored copy (identical histogram, different structure)446 structure — rejected
T3Scale invariance — 128×128 vs its own 64×64 downscale992
T4Monotonicity — corruption 10% / 30% / 60%465 → 208 → 94
T5MIN headline — a strong channel may never flatter a weak one(900,300)→300, never the mean
T6Fail-closed — an unreadable image refuses instead of scoringrefused

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.

Call it

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.

Closing the loop: nx_converge

A 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.

stepdenoisemeasured retentiontime
150066651s
225088230s
337579839s
431284536s

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.

What is still open — honestly

GapState
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 channelpresent and invoked, but crashes: the positional encoding is not extended for the concatenated reference latents
Identity retention metricthis ruler scores whole frames; face-region identity is not yet isolated
Edit-compliance judgemissing — and its absence is why retention-only convergence lands on "change nothing"
Masked-inpaint edit channelthe 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.

Result: identity conditioning works, and it was never about downloads

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.

referenceretention vs the referencewhat you see
off24prompt honoured — and a completely different person
on507our 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.

Resolved: reference strength is now a graded control

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 weightretention vs referencewhat you see
1.0516reference dominates; prompt has no effect
0.6464blending begins
0.3330identity 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.

Superseded: how it looked when the channel was still binary

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:

knobmeasured effect on retentionverdict
reference index offset (on / off)504 vs 508noise — it re-indexes positions, it does not weight
denoise 0.4 / 0.6 / 0.8 / 1.0576 / 569 / 526 / 516a 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.