Per-phase gpt-image-2 title cards composited via Remotion, tested against 4 real, already-rendered Gymshark product ads spanning Reels, Stories, Feed and PMax landscape. Flag ADGEN_DIRECTOR_TITLE_CARDS stays off pending the fixes below.
2 defects · not ready to ship
0:01.9hook
0:05.2proof
0:07.2closeAll 3 timestamps render the hook card. The proof/close cards were generated and billed but never appear — at 7.2s the close frame still reads “The top for your run and your coffee run” instead of the product name + benefits, and it now sits directly over the shark logo.
0:01.2hook
0:05.7closeSame sequencing defect as Reels — the close-phase timestamp still shows the hook headline. Confirms this isn’t a Reels-specific quirk; it reproduces on every 3-phase vertical format tested.
single cardmainSingle-phase format has nothing to sequence. Clean two-weight typesetting, correctly inset to the square safe zone, no collision with the logo mark. This is the case the feature already gets right.
0:00.8phase 1
0:08.3phase 2Cards do switch here — but phase 1 renders as a dithered, washed-out ghost instead of crisp text (an alpha-compositing artifact), and phase 2’s benefits copy overflows past the bottom of frame while still double-exposed against phase 1’s residue.
On both 3-phase vertical ads (Reels, Stories), proof/close cards are generated and billed via a real gpt-image-2 call, then never displayed — the composite holds the hook card’s image for the full render regardless of the sampled timestamp. Paid content silently never reaches the viewer on the two most common ad formats. The 2-phase landscape case does advance, so the defect is specific to how 3+ sequential phases wire into the composite, not the fade/timing envelope itself.
repro: ad 6a9c65e6…50c8 & 6a9c65e8…50f5, timestamps 5.2s / 7.2s & 5.7sFirst card composites as a dithered, checkerboard-alpha ghost instead of opaque text. Second card’s benefits copy runs past the bottom edge of frame and shows a visible double-exposure against the first card’s fade-out.
repro: ad 6a9c65e5…50b2, frames at 0.8s & 8.3sA tight crop on the Reels close-phase frame shows generated title copy sitting directly over the stitched Gymshark logo. The card is generated blind to the actual video frame, so this class of collision is a structural risk of the approach rather than a one-off render.
repro: ad 6a9c65e6…50c8, frame at 7.2sReels/Stories safe zones were split from one collapsed vertical entry, the
keep-out band gap is closed, and safeZoneKey now flows through the CJS plate-scan
path in production (adgen #59 / backend #335). The one loose end is a stale line in
session.d/KNOWN-OPEN.md still describing it as open — code disagrees with
the doc, not the other way around.
| Ad | Format | Cards | Spend | Result |
|---|---|---|---|---|
| 6a9c65e6…50c8 | Reels 9:16 | 3 | $0.1806 | sequencing bug |
| 6a9c65e8…50f5 | Stories 9:16 | 3 | $0.1304 | sequencing bug |
| 6a9c65ea…5113 | Feed 1:1 | 1 | $0.0100 | pass |
| 6a9c65e5…50b2 | PMax 16:9 | 2 | $0.0702 | composite artifact |
| Total | $0.3912 | |||
scripts/renderDirectorTitleCardsPreview.js) drives
the real directorTitleCardService + directorTitleCardGenerate
+ Remotion TitleCardSlot stack against each ad’s existing untitled master
(veoVideoUrl), read-only against the production database — Ad-collection writes
are blocked at both the driver and model level, and adId is omitted from every
generate call so no spend receipt lands on the ad. Videos and stills here are re-encoded for file size; visual
defects reproduce at source resolution.