Character consistency in image generation is usually discussed as an aesthetic problem. I find it more useful to treat it as a contract problem: I hand a model a specification, and I want to know whether the specification survives a change of inputs. So I built a spec with a deliberate failure mode and ran it through two anime image models on the same day.
The failure mode is symmetry.
The spec
Seven attributes, written once, held constant across every run.
character: Shiori
hair: jet-black, low knot at the nape
hair_pin: single thin gold pin, through the knot
eyes: pale grey
face_mark: small beauty mark, below RIGHT eye
outfit: black high-collared tailcoat, gold piping, burgundy lining
shirt: white wing-collar, burgundy ribbon tie
glove: LEFT hand only
ring: gold, RIGHT index finger, on bare skin
Five of these are cheap. Hair color, eye color, and a palette are the kind of thing a diffusion model reproduces almost by accident, because they are global properties of the image.
Two of them are expensive, and they are the assertions that matter:
glove_hand == LEFT and ring_hand == RIGHTface_mark_side == RIGHT
Both are asymmetric. Asymmetry is the interesting test because symmetry is the prior. A model that has stopped tracking your description will not produce noise; it will produce the average, and the average of "one glove" is either two gloves or none, or both attributes collapsed onto whichever hand it happened to render.
There is a third assertion that turned out to matter more than I expected, and I did not write it down at first:
both_hands_in_frame == true
An attribute you cannot see is an attribute you cannot verify, and a model that crops the frame has silently deleted your test.
Test 1: baseline, prompt only
Model A is the Niji Journey model, currently Niji 7. Model B is Tsubaki.2 on PixAI, a diffusion transformer, which matters later because bracket weight syntax like (tag:1.3) works on SDXL-family models and does nothing on a DiT.
Both baselines passed the expensive assertion. Glove and ring landed on separate hands. Model A mirrored the sides, which is a known and boring class of failure. Model B put them where the spec said.
Baseline passing is worth stating plainly, because a lot of comparisons stop here and declare a winner on the strength of a first render. First renders are the easy case. The prompt is doing all the work and nothing has been asked to persist.
Test 2: change the inputs, keep the spec
New outfit, new environment, same seven attributes carried into the prompt verbatim: heavy overcoat, knit scarf, rain-slicked street at dusk, full body.
| Run | Model | glove/ring separated | both hands in frame | unrequested additions |
|---|---|---|---|---|
| 2a | A | no, merged onto one hand | no, waist-up crop | gold hoop earring |
| 2b | A, identical prompt | no, merged onto one hand | no, waist-up crop | gold hoop earring |
Run 2b is the useful one. I repeated 2a byte for byte specifically to find out whether the failure was stochastic or deterministic in effect, and it reproduced. That distinction is the whole engineering point:
- A stochastic failure is a retry problem. Loop until green.
- A reproducible failure is an input problem. The same prompt will produce the same misreading indefinitely.
The both_hands_in_frame assertion failing is what caused the primary assertion to fail. With one hand cropped out, the model had one surface for two mutually exclusive attributes and merged them. My spec had a hole in it, and the tool found the hole.
Test 3: reference as input
At this point the correct move is to stop describing the character and start passing it. Model A exposes a Character Reference control. On Niji 7 it renders disabled, and clicking it emits nothing, no error, no tooltip, no explanation. The references panel offers an image prompt and a style reference.
So I used the image prompt, feeding the passing baseline back in at default weight.
| Run | Input | Result |
|---|---|---|
| 3a | baseline image as image prompt | face fidelity best of all runs, palette bled (jacket lining reappeared as a turtleneck), glove/ring still merged, still cropped |
This is the finding I care about. The reference improved everything that is a global property of the image and improved nothing that is a discrete fact about the subject. Treating "reference image" as a single capability is a mistake. There are at least two different contracts hiding under that label, and only one of them carries a specification.
Model B routes this through a separate model rather than a flag on the generation model, which is an implementation detail with a real consequence: the character is created on an anime model and then carried by a reference model, so a style shift between the two is expected rather than a bug. It accepts up to ten reference inputs, which fits how character sheets work, since a character is a set of angles rather than one canonical frame.
Across three scene changes on Model B, the face, the knot, and the gold pin held.
Test 4: adding a LoRA, and its cost
A LoRA is a low-rank adapter, a small set of weights trained on top of a base model. I added a community adapter built for eye rendering, typed its trigger words into an otherwise unchanged prompt, and ran it at 0.7.
It delivered the eye treatment. It also softened the face geometry and added bangs that appear nowhere in the spec. At 0.7, the adapter reached past style and into identity.
The generalizable rule: an adapter is a weighted intervention on the same latent space your character occupies, so strength is a tradeoff between the effect you want and the subject you are trying to preserve. If identity is the priority, the weight is the first knob to turn down.
What I take from this
Three things transfer to anything you build on top of image models.
Put an asymmetric assertion in your spec. It is a one-token change, and it converts a subjective "does this look like her" into a boolean. Symmetric attributes will pass even when the model has stopped listening.
Assert your frame. An attribute outside the crop is untested, and an untested attribute fails silently. Naming the framing explicitly fixed more for me than any prompt-weight trick.
Distinguish description from identity. A prompt is a description and descriptions are satisfiable by more than one subject. Persistence needs the character passed as data, either as reference input or as trained weights. Anything short of that is a retry loop with a fixed point in the wrong place.
The tooling question resolves along the same line. If the deliverable is one image, the model with the better first render wins, and in my runs that was Model A. If the deliverable is the same subject across many images, what matters is whether the platform gives you a path to pass identity as data, and that is why the second half of my day happened on PixAI.




Top comments (0)