Type a mobile-game name β get a full competitive creative dossier (deconstructed competitor ads, archetypes, ready-to-render briefs) in 3β5 minutes, grounded in fresh SensorTower signals.
β Four of the per-game branded endcards VoodRadar pre-generates (GPT Image 2 for the static frame β Kling i2v for the 3-second animation). Each title's endcard is rendered once and grafted onto every variant via lastFrameImage chaining, so a 3-clip Kling video transitions into a CTA frame that's already on-brand for the game.
As a Voodoo PM managing dozens of live games, I want a tool that flags which titles need a creative refresh, deconstructs what is currently winning in their category, and produces a small set of on-brand video variants I can take to A/B test β without spending a half-day per title.
VoodRadar turns a single game name into a full competitive creative dossier in 3 to 5 minutes (rendering each brief into a finished video is a separate, optional step that takes another few minutes per variant):
- Pulls the live SensorTower top-creatives feed (4 networks Γ 7 countries) and indexes hundreds of ads.
- Deconstructs every creative with Gemini Pro Vision β extracting the hook, the scene flow, the text overlays, the CTA, the palette, the audience proxy.
- Clusters the corpus into archetypes via signal-weighted scoring (SoV velocity, derivative spread, freshness, network diversity).
- Scores each archetype against the target Game DNA on visual / mechanic / audience axes (Claude Opus 4.7).
- Authors per-archetype creative briefs with explicit audio directives β including bespoke, high-tempo narration scripts written for each variant (no Lorem-Ipsum filler, no generic copy).
- Renders 3 hero frames via Scenario (gpt-image-2), then 3 parallel 5-second Kling i2v video calls, with explicit
firstFrame/lastFramechaining on the final clip so it transitions seamlessly into the pre-generated game-specific endcard. - Mixes a multi-layer audio track via
ffmpeg filter_complex: music bed (auto-ducked to 25%) + Opus-authored narration β OpenAI TTS + timestamp-spliced game SFX. - Outputs an 18-second branded MP4 ready to upload to Meta Ads / TikTok.
A design choice we leaned on heavily: every Gemini call is persisted in a knowledge base keyed by creative_id. The first analysis pays Gemini; subsequent ones (cross-game, cross-week, cross-machine) hit disk in <10 ms. The repo ships with 499 ads pre-deconstructed, so a fresh clone has a usable corpus from the start.
We don't just rank by raw Share-of-Voice. SensorTower's top-creatives feed is heavily skewed to long-running brand campaigns; what a Voodoo PM actually needs is what's working right now in their category. So we score every creative on a weighted composite of four signals:
| Signal | What it captures | Source |
|---|---|---|
sov_velocity |
4-week derivative of share-of-voice for the advertiser. Positive velocity = the creative's parent campaign is gaining ground; negative = decaying. | /v1/unified/ad_intel/network_analysis per app_id, sliced into rolling weekly buckets. |
derivative_spread |
Variance of velocity across networks. High spread = the campaign is exploding on one network and flat on others (signal of channel-specific virality). Low spread = uniform, less informative. | Computed from the same series, network-level slices. |
freshness |
Days since first_seen_at. Decay function: full weight β€ 7 days, half-weight at 30 days, ~0 at 90 days. Catches "this hook just launched and is already top-N". |
ad_unit.first_seen_at from creatives_top. |
network_diversity |
Number of distinct networks running the same phashion_group (visual hash). Genuine winners propagate across Meta / TikTok / AppLovin. Single-network outliers are usually network-specific tests. |
phashion_group + network fields. |
Final score is (0.45 Γ velocity_norm) + (0.20 Γ spread_norm) + (0.20 Γ freshness_norm) + (0.15 Γ diversity_norm). The weights are heuristic β picked to favour velocity over freshness over diversity β and would benefit from being calibrated against a labelled set of historical winners in a follow-up. Implementation in app/analysis/archetypes.py.
The intent is to surface the handful of ads where the signal genuinely justifies attention rather than dumping a full top-50 list on the PM, with each score component visible on the card so the choice is auditable.
Every selected creative goes through Gemini 2.5 Pro Vision (1M context, native multi-frame video understanding). The prompt asks for a structured Pydantic schema so we never deal with prose:
class DeconstructedCreative(BaseModel):
creative_id: str
hook_frame: HookFrame # 0-3s: visual subject + spoken hook + on-screen text
scene_flow: list[SceneBeat] # 5-8 beats with timestamps + camera + action
text_overlays: list[TextOverlay] # every text-on-frame with timing + position
voiceover_transcript: str | None # full transcript when audio present
cta_frame: CtaFrame # the final-2s endcard structure
palette: ColorPalette # 5 dominant colors with hex + role
emotional_pitch: Literal[
"satisfaction", "fail", "curiosity", "social-proof",
"tutorial", "live-action-ugc", "in-game", "animation", "other",
]
archetype_label: str # free-form, e.g. "live-action UGC reaction"
audience_proxy: str # "20-something casual gamer, browsing TikTok"
pacing_score: float # 0-1, normalized scene-cuts-per-secondEach deconstruction is written to data/cache/deconstruct/<creative_id>.json and never recomputed unless the file is removed. After the hackathon, the knowledge base contains 499 creatives Γ ~3 KB each β 2 MB of structured market intelligence built up over the hackathon weekend. A scripts/scan_top_competitors.py cron is set up to keep it fresh on a weekly cadence.
This is what makes the second analysis on a Voodoo title 10Γ cheaper than the first β the per-archetype clustering reads from disk instead of re-paying Gemini.
The clustering step picks the 3 archetypes that score highest for this specific game (game-fit ranker, also Opus, scoring on visual / mechanic / audience axes). Each goes to a separate Opus 4.7 call that produces a fully-typed CreativeBrief:
- 3 storyboard frames (each with prompt for Scenario, plus negative prompt)
- per-frame text overlays
- final CTA copy
audio_directiveβ the section of the prompt that drives the audio outputvibe_track: which emotional pitch to match for the music bednarration_script: a bespoke 3-sentence script Opus writes specifically for the variant β short cadence, punchy nouns, structured as setup β reveal β CTA, calibrated to the snappy delivery that performs on TikTok / Reelssfx_cues: 3-5 timestamped game-feel sound effects (whoosh @ 1.2s,drop @ 4.8s,chime @ 17.5s)
Including this directive in the prompt gave us audio that fits the casual-arcade tone Voodoo ads typically run on, rather than a flat reading of the text overlays. We compared both approaches internally before settling on this design.
The video-rendering step takes one brief and produces an 18-second branded MP4. End-to-end time depends mostly on how busy the Scenario backend is β typically a few minutes per variant when run uncached:
ββββββββ Scenario gpt-image-2 (parallel Γ3) ββββββββ
β β
brief.frame_prompts βββΌββ frame_0.png βββ β
βββ frame_1.png βββΌβββ 3 hero frames (1080Γ1916) β
βββ frame_2.png βββ β
β
ββββββββ Kling i2v (parallel Γ3) βββββββββββββββββββ
β
clip_0 = i2v(frame_0) β 5s, no constraints
clip_1 = i2v(frame_1) β 5s, no constraints
clip_2 = i2v( β 5s, BUT:
firstFrameImage = frame_2,
lastFrameImage = endcard_first_frame β grafts onto the endcard
)
β
βΌ
ffmpeg concat: clip_0 β clip_1 β clip_2 β endcard.mp4 (18s total)
β
βΌ
ffmpeg filter_complex (multi-layer audio mix β section 5)
β
βΌ
final.mp4
The first/last-frame chaining on clip_2 is what gives the cut into the branded endcard a clean transition. Kling's i2v supports passing both firstFrameImage and lastFrameImage (in Fast mode β Rich/2.6-Pro mode rejects the combo when generateAudio=true, so in that mode we accept a hard cut in exchange for native diegetic audio). We extract the endcard's first frame at pipeline-init time and reuse it across every variant for the same game.
Implementation in app/creative/scenario.py (the API client) and api/main.py:render_variant_video (the orchestrator).
Three layers, mixed down into one stereo track:
| Layer | Source | Volume | Strategy |
|---|---|---|---|
| Music bed | Stock track from data/cache/audio/library/<vibe>.mp3 (Pixabay CC0). The vibe is picked from the brief's emotional_pitch (satisfaction β energetic chiptune, fail β comedic glitch, live-action-ugc β upbeat lo-fi, etc). |
25% | Looped to video duration with apad=whole_dur=... so it never truncates. Auto-ducks to 25% the moment voice is enabled. |
| Voiceover | OpenAI TTS (alloy voice) reading the Opus-authored bespoke narration β not the text overlays. The narration is cached on disk per (brief_hash, voice_id) so re-rolls are free. |
100% | Padded with silence to the full video duration so amix can't drop early. |
| Game SFX | 5 short stems (whoosh.mp3, swoosh.mp3, drop.mp3, chime.mp3, brand.mp3) timestamp-spliced at the brief's sfx_cues. |
80% | Each splice uses `adelay= |
The filter graph is built dynamically: each enabled layer gets its own input slot, its own chain, and a label that feeds into the final amix=inputs=N. An early implementation used -shortest, which truncated 18s videos down to the 4s narration; the current version apads every audio source to the video length so amix aligns correctly. Implementation in api/main.py:_try_apply_audio_layers.
The UI exposes three toggles (Music / Voice / SFX) and the backend keeps a silent copy of the assembled video as .silent.mp4, so the architecture supports re-mixing audio without re-running the video pipeline. Validating that fast-path end-to-end was on our shortlist for the hackathon and remains a follow-up β the rendering itself currently runs the full pipeline on each click.
Every Voodoo title gets a branded endcard generated once and reused across every variant:
Pipeline scripts: scripts/generate_endcards.py for the static gen, scripts/animate_endcards.py for the animation pass (with auto-trim of the empty 2 last seconds + 429 backoff). The --all flag is idempotent β already-animated games are skipped.
We pre-generated 17 animated endcards covering every Voodoo title in our demo set. Adding a new one is uv run python -m scripts.generate_endcards --game "<game name>" && uv run python -m scripts.animate_endcards --game "<game name>".
βββββββββββββββββββββββββββββββββββββββββββ
β Frontend (React + TanStack Router) β
β / /voodoo /ads /insights /weekly β
β /competitive /competitor/$appId β
β /performance /geo /ad/$id β
βββββββββββββββββββ¬ββββββββββββββββββββββββ
β HTTP + Server-Sent Events
βββββββββββββββββββΌββββββββββββββββββββββββ
β FastAPI bridge (api/main.py) β
β /api/report /api/variants/render-videoβ
β /api/weekly-report /api/creatives/{id}β
β /api/competitor/{app_id} β
β /api/report/run/stream (SSE pipeline) β
ββββββ¬ββββββββ¬βββββββ¬βββββββ¬βββββββββββββββ
β β β β
βΌ βΌ βΌ βΌ
βββββββββββββ ββββββββ ββββββββ ββββββββββββ
βSensorTowerβ βGeminiβ βOpus β βScenario β
β ad-intel β β Pro β β 4.7 β βimg + i2v β
βββββββ¬ββββββ βββββ¬βββ βββββ¬βββ βββββββ¬βββββ
β β β β
βββββββββββββ΄βββββββββ΄βββββββββββ
β
ββββββββββΌββββββββββββββββββ
β data/cache/ β
β reports/ game_dna/ β
β briefs/ deconstruct/β
β endcards/ videos/ β
β audio/ scenario/ β
β voodoo/ sensortower/β
ββββββββββββββββββββββββββββ
Knowledge base (data/cache/deconstruct/) is where the persistence lives: every Gemini call keyed by creative_id, cached on disk. The first analysis pays Gemini, subsequent ones hit disk in <10 ms. A weekly cron of scripts/scan_top_competitors.py keeps it fresh.
api/main.py # FastAPI: 20+ endpoints + SSE pipeline runner
app/
βββ models.py # Pydantic data contract (the lingua franca)
βββ pipeline.py # 10-step pipeline orchestrator
βββ analysis/
β βββ game_dna.py # SensorTower meta + Gemini Vision on screenshots
β βββ deconstruct.py # Gemini Pro Vision per-creative dossier
β βββ archetypes.py # signal-weighted clustering (velocity / spread / freshness)
β βββ game_fit.py # Opus per-archetype game-fit scoring
βββ creative/
β βββ brief.py # Opus brief authoring with audio directives
β βββ scenario.py # Scenario REST client (img + i2v + lastFrame chaining)
β βββ video_brief.py # Veo 3 alt path
βββ sources/
βββ sensortower.py # SensorTower /ad_intel + /search wrapper
βββ voodoo.py # Voodoo catalog harvester (50 games)
front/ # React app (TanStack Router + Tailwind + shadcn/ui)
βββ src/components/dashboard/ # Page-level views (Insights, AdLibrary, β¦)
βββ src/components/insights/ # GeneratedAdSection, LiveAnalysisView, β¦
βββ src/lib/ # API hooks + game / pipeline-runs context
βββ src/routes/ # File-based routing (incl. /competitor/$appId)
scripts/
βββ precache.py # Pre-bake a HookLensReport for one game
βββ precache_voodoo_ads.py # Snapshot the 50-game Voodoo portfolio
βββ scan_top_competitors.py # Backfill the deconstruction knowledge base
βββ generate_endcards.py # GPT Image 2 β branded endcard PNG
βββ animate_endcards.py # Kling i2v β animated 3-second endcard MP4
βββ generate_demo_video.py # CLI multi-clip ad assembly (pre-React)
data/cache/ # All cached state (selectively gitignored)
βββ reports/ # 21 cached HookLensReports (~0.6 MB)
βββ deconstruct/ # 499 Gemini deconstructions (~1.9 MB)
βββ endcards/ # 17 animated game endcards (PNG + 3-second MP4)
βββ voodoo/ # 50-game Voodoo catalog + portfolio snapshot
βββ sensortower/ # Raw SensorTower API responses
βββ audio/library/ # Stock music keyed by emotional pitch
βββ audio/sfx/ # Game SFX stems
βββ audio/tts/ # OpenAI TTS cache
- Python 3.12 (pinned in
.python-version) uvfor package management- Node 20+ for the frontend
ffmpeg+ffprobeon$PATH- API keys in
.env:SENSORTOWER_API_KEYGEMINI_API_KEY(Gemini Pro Vision)ANTHROPIC_API_KEY(Claude Opus 4.7)SCENARIO_API_KEY+SCENARIO_API_SECRET(image + video generation)OPENAI_API_KEY(TTS voiceovers)
uv pip install -e ".[dev]"
cd front && npm install --legacy-peer-deps && cd ..
# Terminal 1 β backend
uv run uvicorn api.main:app --reload --port 8000
# Terminal 2 β frontend
cd front && npm run dev # β http://localhost:8080The cached state ships with the repo (~125 MB tracked), so a fresh clone has 21 analyzed games + 499 deconstructed ads + 17 animated brand endcards available without paying any API.
uv run python -m scripts.precache "<game name>"Burns ~$0.50β$1 in API calls and takes 3β5 minutes. The result lands in data/cache/reports/<app_id>_e2e.json and shows up in the React UI immediately.
uv run python -m scripts.scan_top_competitors --concurrency 5Walks every cached SensorTower creative, deconstructs the ones not yet in the knowledge base. Idempotent β second run is a no-op.
- Hero report β http://localhost:8080/insights β "Crowd City" (21 cached reports available; Crowd City has the most polished variants + an animated endcard).
- Generate Ad β on any cached report, click Generate Ad in the "Generated ad video" section. End-to-end render takes a few minutes per variant (parallel Scenario calls + Kling i2v are the bottleneck).
- Knowledge base β http://localhost:8080/weekly β 499 deconstructed ads, distribution by emotional pitch, click any tile for its Gemini dossier on
/ad/<id>. - Competitor deep-dive β http://localhost:8080/competitive β click any top advertiser β live SensorTower fetch of their full ad inventory + cached deconstructions.
| Layer | Tool |
|---|---|
| Backend | Python 3.12 Β· FastAPI Β· Pydantic 2 Β· httpx async Β· asyncio + Semaphore |
| Frontend | React 19 Β· Vite Β· TanStack Router Β· TanStack Query Β· Tailwind CSS Β· shadcn/ui |
| Data | SensorTower (ad-intel + search + apps) Β· App Store screenshots (CDN) |
| AI | Gemini 2.5 Pro Vision Β· Claude Opus 4.7 Β· OpenAI TTS Β· Scenario (gpt-image-2 + Kling O1/2.6-Pro i2v + Veo 3) |
| Video | ffmpeg (concat demuxer + filter_complex amix) |
| Audio | OpenAI TTS Β· Pixabay CC0 / Mixkit no-attribution |
| Caching | Flat JSON files keyed by creative_id / app_id / archetype / hash |
| Streaming | Server-Sent Events for live pipeline progress |
An Archery Clash ad VoodRadar produced for the demo: 3 Kling i2v clips concatenated, with a lastFrame graft into the branded endcard. Opus-authored narration sits on top of an auto-ducked music bed and three timestamp-spliced game SFX. The pipeline runs end-to-end from the brief to this MP4.
Built for Voodoo Γ Anthropic Hack 2026 in 30 hours by Tom, Mehdi and myself.
Powered by Anthropic credits (Claude Opus 4.7), Google AI credits (Gemini 2.5 Pro Vision), and Scenario credits (gpt-image-2 + Kling O1/2.6-Pro i2v + Veo 3). SensorTower data used under hackathon-sponsor credentials.
Audio assets sourced from the royalty-free Pixabay library, mapped to emotional-pitch slots in data/cache/audio/library/README.md.
π Track 3 β Market Intelligence β 1st place β Voodoo Γ Anthropic Hack 2026.

