0045 — Anchored sequence generation (visual continuity across panels)¶
Status: APPROVED — decisions resolved with Matt 2026-07-27; implementing. From issue #2, corrected against the session transcript that produced the burnout reel (§2 — the issue text was right, but the failure is subtler than "the panels varied", and the correction changes the design). Date: 2026-07-27 Relates: 0013 (the generate/poll/takes/pick engine) · 0006 (the existing image-reference path) · 0031 (spend ledger) · 0044 (card mode — a sequence is only visible once overlay cards actually render) · issue #2.
1. Problem¶
cards gen generates each card independently, so a multi-card reel has no visual
continuity: style, palette darkness, figure and framing jump card to card. For a
narrative reel — one evolving scene across panels — the result reads as a jumble.
This is not a prompt-quality problem. Independent generation cannot hold a look across N calls however good the per-card prompt is, because nothing carries between calls.
2. What was actually proven (transcript-verified)¶
The burnout reel (LinkedIn cut, emberlight theme) went through three
stages, not two. The middle one matters most, and is the part easily
misremembered.
Stage 1 — independent generation: incoherent¶
Each card generated in isolation; darkness a dice-roll per panel.
"tonally we have a shift from super dark to the petrol teal, which I dont mind as a progressive shift over the course of the panels, but it needs to be sequential, not jumping around like it is now"
Stage 2 — chained (each panel references the previous): consistent but CLONED¶
Proven first with OpenAI/codex, then reproduced with Gemini. It fixed consistency — and introduced a new failure:
"chaining each panel off the last kept them consistent but also cloned them, and the later beats got cramped."
Panels 1 and 2 came out near-identical and the later beats had no room. Deep chaining is therefore not the answer: it over-constrains composition.
Stage 3 — single style anchor + deliberate per-beat variation: the fix¶
Two changes together, both necessary:
"(1) anchor every panel to a single style reference instead of chaining, so compositions are free to differ while the look holds, and (2) deliberately vary the shot scale and negative space per beat — wides that breathe …, an intimate close, an extreme hand close-up — so the sequence has rhythm."
The regeneration landed: distinct beats, varied scale, room to breathe.
Design consequence: per-beat shot-scale / negative-space variation is half the fix, not a refinement. An anchor alone yields consistent-but-cramped clones — stage 2 with extra steps. Any implementation that ships the anchor without deliberate per-beat framing variation reproduces the cloning.
Model, and the aspect trap¶
gemini-3-pro-image (Nano Banana Pro) won a three-way comparison against codex
and Gemini 2.5 — richest, warmest, held the arc. And:
"lock every panel to 9:16 (a couple of the Pro test panels drifted wider)"
That corroborates a real gap in our own adapter (§3.1).
There was no "generate all panels in one prompt" step. The final 12 panels were generated sequentially, each anchored to one style reference. Recorded explicitly because it is the natural thing to misremember, and chasing it would be an expensive design for no gain.
3. Scope — smaller than issue #2 implies¶
Three of the four capabilities the issue asks for already exist:
| Capability | State |
|---|---|
| Image references on generation | ✅ provider.ImageRequest.Refs; the Gemini adapter maps them to inline_data parts (gemini.go:153) — the avatar path |
gemini-3-pro-image |
✅ already in the model chain (gemini.go:35), selectable via req.Model / config |
responseModalities: ["IMAGE"] |
✅ already sent (gemini.go:265) |
imageConfig.aspectRatio |
❌ missing on the generateContent path |
3.1 Gap: aspect is never sent on the reference path¶
Aspect reaches only the imagen path (gemini.go:237). The
generateContent path — which every reference-image request uses, including
today's avatar generation — sends no aspect config at all. So an anchored 9:16
panel has no aspect guarantee, which is exactly the drift observed.
This is a standalone bug: it already affects avatar generation, independent of sequencing. It lands first, on its own.
3.2 The sequence itself¶
Generate panels in board order, each call carrying:
- that card's own
sceneprompt, - the anchor as an image reference (
Refs), - an explicit shot-scale / negative-space directive for that beat (D6),
- the target aspect (§3.1).
One anchor for every panel — never the previous panel.
4. Decisions — RESOLVED with Matt 2026-07-27¶
- D1 — The anchor is panel 1, generated first, then reused for 2..N. Least ceremony, no extra asset to manage. Because a weak panel 1 poisons the whole run, it needs a cheap re-roll-the-anchor path before committing the remaining N−1 generations.
- D2 — Sequence mode is opt-in via a reel THEME setting, not a per-invocation flag. A narrative theme declares its cards are a sequence; kinetic-typography themes keep independent generation. Nothing to remember per run, and it follows 0001 §6 (thematic behaviour is config, never a constant).
- D3 — A re-roll of panel K re-runs only panel K. Anchoring rather than chaining makes each panel independent given the anchor — which is precisely why this model preserves the existing authoring loop. Re-rolling the anchor invalidates the set, and must say so.
- D4 — Estimate the whole run before the first call. Committing to N
generations at once is materially different from one; 0031's per-request guard
(
spend.confirm_above) is the wrong granularity on its own. - D5 — The anchor is an input, not a candidate. It does not belong in
cards/takes/, but must live somewhere the workspace owns andmedia pushcarries. - D6 — Per-beat variation is mandatory, not optional (§2 stage 3). The request builder emits an explicit shot-scale / negative-space directive per card. Shipping the anchor alone reproduces the cloning, which is the failure this spec exists to avoid.
5. Scope / non-goals¶
- Not OpenAI as an image backend (issue #2 item 3, explicitly lower priority; the transcript's own conclusion was that Gemini does the trick keryx needs).
- Not a change to picking/promotion — sequence mode produces candidates the existing take gallery handles unchanged.
- Not video/panel motion (0003, deferred to Phase 5).
6. Phasing¶
- P1 — the aspect fix (§3.1). IMPLEMENTED 2026-07-27.
genConfiggained an optionalimageConfig.aspectRatio, threaded through everygenerateContentpath (image-to-image, the forced-model route, and the gemini-* text-to-image fallback). Omitted entirely when no aspect is requested — an emptyaspectRatiois not the same as the model's default. - P2 — the sequence builder. IMPLEMENTED 2026-07-27.
theme.Cardgainedsequence+shots;planSequencehandles the anchor lifecycle (card 1 alone until picked, then the rest against it, never regenerating the anchor on a whole-board run);shotFor/withShotcycle a per-beat framing directive into each prompt; the anchor is attached as an ADDITIVE reference so an avatar card keeps its cast. Deferred: the whole-run spend estimate (D4) — it needs estimate plumbingcards gendoes not have today, and is worth its own slice rather than being bolted on here.
7. Test plan¶
- Adapter (faked HTTP):
generateContentsendsimageConfig.aspectRatiowhen an aspect is requested and omits it otherwise; the avatar path keeps working. (P1) - Unit (sequence builder): N requests in board order, each carrying the anchor ref, that card's scene, and a distinct shot-scale directive; a re-roll of panel K emits exactly one request with the same anchor (D3).
- Unit (theme): sequence mode is read from the reel theme (D2); a theme without it generates independently, as now.
- Spend: a whole-run estimate is produced before the first call (D4) —
DEFERRED with P2;
cards genhas no estimate path today. - Integration (env-gated,
INT_TEST=1): a short real sequence ongemini-3-pro-image, asserting N images all at 9:16. - Taste gate (manual): the feature's point is a judgement on a real reel; no automated test substitutes for it.