DOGMA v39 Ghost Veto
When the generated image quietly restages the scene
- generated
- source
- image
- evidence_weight
- info
The nastiest output of a generative pass isn't a deformed hand. It's the version that looks sharp, passes a glance, and has moved the furniture. Shadows pointing the other way, a wall that's now a slightly different colour, a building that's put on a floor. Nothing is obviously wrong; the picture is just no longer the same picture.
This gate is aimed at that. It's the third generation of the source-evidence check in this pack, and the new work over v36 is a low-frequency structure term plus one hard rule.
The added signals
It keeps the same skeleton: support from the source's local gradient and deviation, normalised between low_threshold and high_threshold. It keeps the novelty penalty, softened slightly (source gradient weighted 2.0 instead of 2.2). Then:
slow = box_blur(source, 15) # only broad shapes survive
glow = box_blur(generated, 15)
structure = clamp((|glow - slow| - 0.022) / 0.070) # did the scene-level look change?
broad = clamp((|generated - source| - 0.035) / 0.11)
veto = max(novelty, broad * 0.75, structure)
alpha = (floor + (1 - floor) * support) * (1 - 0.96 * veto * (1 - 0.55 * support))
structure is the interesting one because of that 15-pixel blur. At that scale, edges and texture are gone and what's left is the big shapes and their tonal relationships. A patch that rebuilt a car's panel gaps scores near zero on it. A patch that made the tarmac brighter, or re-shaped which parts of the frame are dark, scores high - because it changed the coarse image, which is what a viewer's eye actually locks onto when they say "something's off."
The hard rule is blunter still:
if structure > 0.72 and support < 0.58: alpha = 0.0
Strong scene-level change in a region with weak source evidence is zeroed outright. Not damped to 4% - the source pixels are returned and the generated version is discarded. The support term softens the veto where the source was genuinely detailed: a real edge gets more latitude, because the model probably had a reason.
Inputs and outputs
Identical to the earlier gates, which is convenient if you're swapping: generated (IMAGE), source (IMAGE), flat_generated_weight, low_threshold, high_threshold, proxy_long_side, support_grow. Defaults are the same as v36's (0.06 / 0.009 / 0.040 / 384 / 4), though the proxy floor is higher internally - the analysis never runs below 128px, where the low-frequency term would be meaningless.
Outputs image, evidence_weight (MASK) and info, and the info line now reports the mean structure value alongside the mean weight. That's the number that tells you whether this gate is earning its keep: high structure means your sampler is restaging the scene, and the fix is upstream - less denoise, more context, a stricter prompt - not more gate.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/axior/ComfyUI-DOGMA-Nodes
ComfyUI Manager → DOGMA Nodes, or comfy node install comfyui-dogma-nodes. Zero dependencies: requirements.txt is one comment line, pyproject.toml says dependencies = [], and everything here is torch tensor math. Nothing to download. MIT licensed.
Where people get burned
Order of operations. This is a gate on an already-generated result, and every extra post-hoc safety net costs you the difference between "the model did it right" and "we averaged it back toward the source." If your architecture is deciding where generation is allowed in the first place, gate less and mask better. Run this where you'd otherwise be fighting ghosting.
The hard zero is invisible in aggregate. A region where alpha is zeroed looks identical to a region where the model just didn't change much. If you suspect the veto is firing, preview evidence_weight - if a chunk of the object's mask region came back near zero, the source pixels were returned and that's your answer. The mean weight in the info line is the coarse check; the mask is the evidence.
And don't run it on a different size than it was generated at. Both the novelty and the structure terms compare generated against source pixel-for-pixel after a bilinear resize, so a resolution or crop mismatch reads as global structural change and the gate will suppress everything. It's the same class of error as feeding a mismatched reference latent: the node does exactly what you asked, it just wasn't what you meant.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| generated | IMAGE | — | |
| source | IMAGE | — | |
| flat_generated_weight | FLOAT | 0.060–0.8 | — |
| low_threshold | FLOAT | 0.0090–0.2 | — |
| high_threshold | FLOAT | 0.0400.001–0.3 | — |
| proxy_long_side | INT | 38496–768 | — |
| support_grow | INT | 40–16 | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| evidence_weight | MASK | — |
| info | STRING | — |