Automation & Agents

Per-Shot Anti-Gacha for AI Video: Stop the Fake Re-roll and Verify With Hashes, Not Your Eyes

For any "try again / next batch" feature, the acceptance test is "the result actually differs," not "it finished running." A silent regression keeps every light green and never raises an error, which makes it the costliest kind of bug.

10/07/2026 | 3 min read | The idea applies to any generation flow — about a 12-minute read

The one idea that matters

For any “retry” or “next batch” feature, correct means the output really changed, not that the call completed.

A button that responds, a loading spinner and new cards on screen don’t prove the image changed. A feature that exists, looks active, does nothing and raises no error is a silent regression. Every light stays green, so it is the hardest kind to catch.

What per-shot anti-gacha is

For each shot in a video, the AI generates 2–4 candidate frames side by side. If you don’t like them, press “another batch,” and when one looks right you pick it. A single gamble becomes choosing the best of a few options.

The trap: the fake re-roll

An image model decides what to draw from a random seed. If “regenerate” doesn’t change the seed and sends the same fixed value every time, ten clicks on “another batch” give you ten identical images. The user believes they are choosing, but the system is redrawing the same picture.

My first version had a default seed of 0 that never changed. It nearly shipped like that.

The fix: design the seeds on purpose

  • Start each batch from a random large integer.
  • Within a batch, offset each candidate’s seed by a large prime (I used 7919).
batch_base = random_big_int()
seeds = [batch_base + i * 7919 for i in range(n_candidates)]

The prime step keeps seeds distinct within a batch and makes collisions across batches very unlikely.

Verification: compute hashes, don’t trust your eyes

Glancing and thinking “looks a bit different” is too easy to fool yourself with. Instead, compute the SHA-256 of each candidate file and compare them. It passes only if all of them differ.

import hashlib

def sha(path):
    return hashlib.sha256(open(path, "rb").read()).hexdigest()

hashes = [sha(p) for p in candidate_paths]
assert len(set(hashes)) == len(hashes), "identical candidates: fake re-roll"

The same rule carries to any retry or regenerate feature: hash two outputs, and count it only if they differ.

The same disease, second variant: per-segment emotion flattened by concatenation

Voice-over works the same way: pick an emotion per segment (neutral, happy, surprised, calm, sad) and audition each one. The trap I hit: the old synthesis path joined the whole script into one string and handed it to the speech engine, so the per-segment emotions were all flattened to neutral the moment they were concatenated. The underlying engine supported per-segment emotion; the upper layer just never passed it down.

General remedy: any per-segment attribute must be passed through every layer, and verified from the actual output (listen, compute), not from whether the settings screen has that field.

Shots with characters: identity-conditioned generation

The reference portrait saved when you locked a character becomes the reference for every shot that includes that character, so face and costume stay put across shots. Each candidate also carries a flag from the AI script supervisor, hinting at which ones may have drawn the wrong intent.

Output: align to real speech duration

How long should each shot stay on screen? Don’t estimate from character count. Use the real duration of that segment’s speech: if the line is 2.9 seconds, the shot is 2.9 seconds, and audio, picture and subtitles stay in sync. Render a review cut first, and only on approval generate the editing-software draft.

Update (added in October)

This setup was built inside ReelFlow, which has been in maintenance mode since August 8 with no new features. But “verify the result really differs on retry” and “pass per-segment attributes through every layer” have nothing to do with that project, and I have kept using both in other generation flows.

When you’re stuck, ask an AI like this

I’m building an AI generation flow with <your tool or framework> and it has a “regenerate / another batch” button. I suspect it doesn’t change the random seed, so repeated clicks give identical results. Here is the code that triggers generation: <paste code>. Please: 1) help me check whether the seed changes on each call, 2) show me how to design batch seeds (random start plus a prime offset), 3) give me a minimal check that compares output hashes. Go step by step, explaining the cause before the example.

In one sentence

Verify that every “next batch / try again” feature really changes the output. Hash two outputs, and count it only if they differ.

Support this work:Support on Ko-fi