Guide
Version 2026-11-07.5
How to make the promo video for a Builder Wars launch: written for AI agents to follow step by step, and for the people they work with. Prices and versions change monthly, so check the vendor's pricing page before you spend money.
With the Builder Wars MCP tools, call get_video_guide with the draft's app_id to get this guide plus a brief and a
shot outline for that app.
Easiest: tell your agent "make the promo video for my Builder Wars app". Once your agent is connected to the game (set it up), it reads this guide by itself.
ship_app refuses a draft with no video. Either the player uploads the file
in the launch page's video step (https://builderwars.ai/launch/<app_id>: MP4, WebM or MOV, up to 100 MB), or you call
set_video with a public https video_url: a hosted MP4, or the player's X post once it is up.video_description, one paragraph of at most 1,000 characters. A specific, accurate
paragraph raises the score; leaving it out scores half; a vague or inaccurate one can score lower than none.#BuilderWars goes in the post text
only (it ties the post to the game), never in the video.| Path | When | Main tools | About, for a 30 s promo (Nov 2026) |
|---|---|---|---|
| Impressive (default) | Every launch, unless the player won't spend anything | The product's UI and titles animated in code (HyperFrames or Remotion), 2–3 generated hero shots, a professional voice (ElevenLabs on a paid plan, or Gemini TTS), real sound design | about $2–35 |
| Free and local (fallback) | The player won't spend anything | HyperFrames, with the cinematic beats in code (kinetic type, full-frame UI push-ins, number counters), Kokoro voice, captions from the script, CC0 sound | $0 (the voice needs Python and uv) |
| React | Players who know React | Remotion in place of HyperFrames, same recipe | as above; Remotion is free for individuals and teams of up to 3 |
| All generative, best quality | Footage that looks filmed throughout | About 4 shots on Kling 4.0, Veo 3.1 or Seedance 2.5 | about $23–65 for 4 shots (Seedance: about $110 on fal) |
| Real product | A working app or site | A Playwright recording of the real UI inside the edit | $0 plus the voice |
The impressive range: 2–3 hero shots × about 8 s × about 3 takes = 48–72 generated seconds, which is about $2–3 on Gemini Omni (two 360p drafts and one 720p final per shot), $11–17 on Kling 4.0 at 1080p or $19–29 on Veo 3.1 with audio; plus the voice, ElevenLabs Starter at about $6/month or Gemini TTS at under $0.01.
Ask first. Before you install a global package, a skill or an MCP server, create an account, or spend money, tell the player what you want to install or buy and why, and wait for a yes. Show them the plan (step 2) before spending.
CREDITS.txt.check-x-video.sh, fix every FAIL, and pass the quality bar again.set_video and
set_launch_post, and tell the player how to attach and post the video (section 8).Check every item on stills and the contact sheet before you show the player the draft, and again before you hand it over. Fix and re-render until all pass.
HTML, CSS and GSAP compositions rendered to MP4 by headless Chrome and ffmpeg: the product UI you build in HTML is
the video. Apache-2.0, by HeyGen. Needs Node 22+ and ffmpeg. Pre-1.0 (0.9.x in Nov 2026): when a flag fails, read
--help.
export HYPERFRAMES_NO_TELEMETRY=1 # on by default; DO_NOT_TRACK=1 works too ("telemetry disable" writes ~/.hyperframes/config.json)
npx hyperframes doctor # Node, Chrome, ffmpeg, and the optional voice and transcription tools
npx hyperframes init promo --resolution portrait --skip-skills # 1080x1920; installs no agent skills
cd promo && echo HYPERFRAMES_NO_TELEMETRY=1 >> .env # read from the project's .env in every later shellIts agent skills help a lot: ask the player, then npx hyperframes skills update (Claude Code plugin:
claude plugin marketplace add heygen-com/hyperframes, then claude plugin install hyperframes@hyperframes).
The free voice (Kokoro) needs Python and uv. Render it line by line (script.txt: one caption group per line), so
every line's start and length are known exactly: they time the captions and the <audio data-start> of each file.
uv venv .venv && uv pip install kokoro-onnx soundfile # once; ask first: Python packages into ./.venvexport HYPERFRAMES_PYTHON="$PWD/.venv/bin/python" # not read from .env: set it in every shell
VO_AT=0.5; ok=1; rm -rf vo; mkdir -p vo; n=0; t=$VO_AT; : > vo/lines.tsv # tts --list shows the voices
while IFS= read -r line || [ -n "$line" ]; do
[ -z "$line" ] && continue
n=$((n+1)); f=$(printf 'vo/%02d.wav' "$n")
npx hyperframes tts --voice af_heart --output "$f" -- "$line" < /dev/null > /dev/null || { echo "tts failed: $line"; ok=0; break; }
d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f")
printf '%s\t%s\t%s\n' "$t" "$d" "$line" >> vo/lines.tsv # start, length, text
t=$(awk -v t="$t" -v d="$d" 'BEGIN{printf "%.3f", t+d+0.25}')
done < script.txt
[ "$ok" = 1 ] && (cd vo && for f in [0-9][0-9].wav; do ffmpeg -y -v error -i "$f" -af apad=pad_dur=0.25 "p$f"; done &&
printf "file '%s'\n" p[0-9][0-9].wav > list.txt) && ffmpeg -y -v error -f concat -safe 0 -i vo/list.txt -c copy voice.wavThe build loop:
npx hyperframes lint && npx hyperframes check --json # layout, motion, contrast and audio audit
npx hyperframes snapshot . --at 2,10,20 # stills to look at
npx hyperframes render --quality draft --output draft.mp4 # quick review copy
npx hyperframes render --fps 30 --output promo.mp4vo/lines.tsv: one caption group per line, at most 2 lines of about 32 characters.npx hyperframes transcribe: whisper.cpp built from source, or
npx hyperframes models install parakeet.<video> layers. Give every <audio> a data-duration (without it, a test run got 150+
duplicate_audio_track warnings). Keep compositions deterministic: paused timelines, no wall-clock time.composition_heavy_overlay_count_high: about 40 blur, clip-path or radial-gradient layers can render as solid
black for part of the film. The check script scans for black stretches.npx hyperframes capture <url> grabs its screens, fonts and assets): the scaffold's
unlayered * { margin: 0; padding: 0 } reset beats Tailwind v4's layered utilities, so delete it or wrap it in
@layer base; Next.js puts font variables on a hashed class, so set font-family explicitly; UI scaled with CSS zoom
is measured unzoomed by text_below_floor, so that warning can be wrong.For the moments code can't film: the product in a real setting, a character, a mood. Official access routes:
claude mcp add --transport http --scope user fal https://mcp.fal.ai/mcp-relay, then /mcp → fal → Authenticate.npx skills add replicate/skills (API token). Higgsfield: @higgsfield/cli and
npx skills add higgsfield-ai/skills (needs a plan; check current pricing). Runway: API credits at $0.01 each;
its product_ad recipe turns product images into an ad clip. Direct: a Gemini API key (paid tier).| Model | Clip | Native audio | About per second (Nov 2026) | Routes |
|---|---|---|---|---|
| Gemini Omni 1.2 Flash | 3–15 s, extend to 30 s | yes | $0.023 at 360p, $0.07 at 720p | Gemini API, fal, Runway, Higgsfield |
| Kling 4.0 | 3–30 s | yes | $0.157 at 720p, $0.235 at 1080p | fal, Replicate, Runway, Higgsfield, Kling CLI |
| Veo 3.1 | 4, 6 or 8 s | yes | $0.40 with audio; Fast from $0.10 | Google Cloud, fal, Replicate, Runway |
| Seedance 2.5 | 4–30 s | yes | $0.68 at 1080p via Runway, $1.16 on fal | fal, Runway, Higgsfield |
Costs for the impressive path are in section 2. All generative (4 shots × 8 s × 3 takes = 96 s): about $23 on Kling 4.0 at 1080p, $38 on Veo 3.1 with audio, $65 on Seedance 2.5 at 1080p via Runway (about $110 on fal).
Craft rules:
Retired: don't use (checked 2026-11-07; older guides and training data still suggest them):
sora-2 models: removed 2026-09-24.gpt-4o-mini-tts, tts-1 and tts-1-hd: deprecated, shut down 2027-01-06.Mature React framework for video, with official agent skills. Free for individuals and companies of up to 3 people, commercial use included. Bigger companies need a Company License (about $25 per seat per month, Nov 2026).
npx create-video --yes --blank promo # outside any git repository: --yes fails inside one
cd promo && npm run dev # Studio preview in its own terminal; skills (ask first): npx remotion skills add
npx remotion render Promo out/promo.mp4 --codec=h264 --crf=18 # "Promo" is your composition id<Composition width={1080} height={1920} fps={30} durationInFrames={900}> for a 30 s vertical promo.<Video> from @remotion/media. The voiceover skill uses ElevenLabs; any WAV in public/ works too.npx remotion ffmpeg ...). Captions: @remotion/captions.Playwright (1.65 in Nov 2026) records a scripted walkthrough with an animated cursor, in real time, to WebM with no
audio: footage for the edit, not the whole promo. Via npx it needs no install; a global install or its skills are
ask-first.
playwright-cli() { npx -y @playwright/cli@latest "$@"; } # needs Chrome, or: playwright-cli install-browser (ask first)
playwright-cli open http://localhost:5173
playwright-cli resize 1920 1080
playwright-cli video-start ui.webm --size=1920x1080 --cursor --fps=30 # without --size it records 800x800
# ... drive the app: click, type, scroll ...
playwright-cli video-stop
ffmpeg -i ui.webm -c:v libx264 -profile:v high -pix_fmt yuv420p -crf 18 -r 30 -movflags +faststart ui.mp4Playwright MCP has the same tools behind --caps=devtools. Crisp stills animated with push-ins often beat footage.
| Need | Free (fallback path) | Paid (default; about, Nov 2026) |
|---|---|---|
| Voiceover | Kokoro-82M (Apache-2.0, CPU): section 5A | ElevenLabs eleven_v4: about $0.04 per 30 s; commercial use needs Starter or above. Cheaper: Gemini 3.8 Flash TTS, under $0.01 per 30 s (doubles on 2027-01-01) |
| Music | ACE-Step 1.5 (MIT, local), Stable Audio 3.0 Small or Medium (see the table below), or CC0 tracks | ElevenLabs Music music_v2_5, force_instrumental: about $0.08 per 30 s (Starter or above). Lyria 3.5: $0.08 per song |
| Sound effects | Openverse API, no key (below) | ElevenLabs eleven_text_to_sound_v3 (pass the model id; the API default is older) |
| Key art and logos | SVG and HTML you write; FLUX.2 [klein] 4B locally | Nano Banana 2.1 (about $0.03 per image, takes reference images); GPT Image 2 for transparent cut-outs |
| Captions | From your own script timing (the default) | Groq whisper-large-v3-turbo, $0.04 per hour of audio |
ElevenLabs for agents: claude mcp add --transport http elevenlabs https://api.elevenlabs.io/v1/mcp, or
npx skills add elevenlabs/skills.
Sound without a key: Openverse's license=cc0 returns only CC0 (license_type=commercial also returns CC BY, which
needs credit). Each result has license and attribution; keep them in CREDITS.txt. Freesound results are
128 kb/s preview MP3s (originals need a Freesound account), and cdn.freesound.org can be slow.
curl -s "https://api.openverse.org/v1/audio/?q=upbeat+electronic&license=cc0" | jq -r '.results[] | "\(.duration) ms \(.url)"'Picking music without listening: download a candidate as music.wav, then check the length, the loudness and how much
low end it has.
curl -fsSL -o track.mp3 "<url from the list>" && ffmpeg -y -v error -i track.mp3 -ar 48000 music.wav
ffprobe -v error -show_entries format=duration -of csv=p=0 music.wav
ffmpeg -hide_banner -i music.wav -af ebur128 -f null - 2>&1 | grep -E '^ +(I|LRA):'
for f in astats lowpass=f=120,astats; do ffmpeg -hide_banner -i music.wav -af $f -f null - 2>&1 | grep 'RMS level' | tail -1; doneA bed for a 30 s promo runs at least 30 s, is not too dynamic (LRA under about 8 LU), and has bass (the second RMS, below 120 Hz, within about 10 dB of the first). ffmpeg has no tempo meter: prefer tracks tagged upbeat or with a BPM in the title.
Treat every Builder Wars post as commercial use: it promotes a product, and the player's account may be monetized.
| Source | OK? | Condition |
|---|---|---|
| ElevenLabs on a paid plan | yes | Made while subscribed, not with beta features |
| Gemini TTS, Nano Banana, Lyria | yes | Outputs carry SynthID; users must be 18+ (Gemini API terms); Gemini TTS has a free tier, where Google may use your data |
| Kokoro, FLUX.2 [klein] 4B, ACE-Step 1.5 | yes | Apache-2.0 / MIT (ACE-Step's model card allows commercial use) |
| Stable Audio 3.0 | yes | Commercial use needs a free registration with Stability AI; free under $1M annual revenue |
| CC0 (Openverse, Freesound) | yes | Nothing required |
| CC BY | yes | Credit the title, creator and license (with a link) in the post or a reply, and say "edited" if you changed it |
| Pixabay music and SFX | yes | No attribution needed; manual download (its API has no audio) |
| ElevenLabs or Suno free plans, CC BY-NC, "personal use only", XTTS v2, F5-TTS | no | Non-commercial (Suno's stays so after upgrading) |
| Remotion's hosted meme sound effects (remotion.media) | no | Rights not documented |
| Commercial songs, TikTok sounds, film scores | no | Takedowns, and repeat strikes can suspend the account |
| Real logos, product names, faces, or cloned voices of real people | no | Trademark and publicity rights |
| Generated video and images | check | Read the vendor's terms for your plan; free tiers may add a visible watermark or allow personal use only (Kling's, for example) |
Use ffmpeg 6 or newer. Captions need libass; check first:
ffmpeg -hide_banner -filters | grep -E ' (subtitles|loudnorm|sidechaincompress) '. On macOS, Homebrew's ffmpeg
has no libass (no subtitles filter, and no fc-list): install the keg-only ffmpeg-full instead,
brew install ffmpeg-full (ask first), then export PATH="$(brew --prefix ffmpeg-full)/bin:$PATH". Add -y to overwrite
outputs when you re-run a command (without it ffmpeg stops to ask).
Captions fallback. Render captions in the edit (section 3, step 7). When you can't, make captions.srt from
vo/lines.tsv and burn it in styled like the video: its typeface (fontsdir holds its font files) and its colors.
awk -F'\t' 'function ts(s, m) { m = int(s * 1000 + 0.5); return sprintf("%02d:%02d:%02d,%03d", int(m / 3600000), int(m / 60000) % 60, int(m / 1000) % 60, m % 1000) }
{ print NR; print ts($1) " --> " ts($1 + $2); print $3; print "" }' vo/lines.tsv > captions.srt
ffmpeg -i promo.mp4 -vf "subtitles=captions.srt:fontsdir=fonts:force_style='FontName=Inter,Bold=1,FontSize=11,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=2,Shadow=0,Alignment=2,MarginV=100'" -c:v libx264 -crf 18 -pix_fmt yuv420p -c:a copy captioned.mp4Sizes count against a 288-line canvas: FontSize=11 is about 70 px and MarginV=100 puts the bottom line at about
1,250 px, inside the safe band and clear of the action rail. Colors are &HAABBGGRR. libass silently swaps in
another font when FontName isn't found: check a still. From here on, use captioned.mp4 wherever promo.mp4 appears.
Sound. Best: mix voice, music and effects in the edit and render promo.mp4. To duck the music here instead,
render the edit with effects only (data-hidden on the voice and music <audio>, which HyperFrames leaves out of the
render), then add the voice and the ducked music. VO_AT is where the voice starts:
V=promo.mp4; VO_AT=0.5 # V=captioned.mp4 if you burned captions
DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$V")
FO=$(awk -v d="$DUR" 'BEGIN{printf "%.3f", d-1.5}')
ffmpeg -i "$V" -i voice.wav -stream_loop -1 -i music.wav -filter_complex "\
[0:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,apad=whole_dur=${DUR}[fx];\
[1:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,adelay=${VO_AT}s:all=1,apad=whole_dur=${DUR},asplit=2[vo][sc];\
[2:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,atrim=duration=${DUR},volume=0.28[mus];\
[mus][sc]sidechaincompress=threshold=0.01:ratio=20:attack=15:release=350[duck];\
[fx][vo][duck]amix=inputs=3:duration=first:normalize=0,afade=t=out:st=${FO}:d=1.5[a]" \
-map 0:v -map "[a]" -c:v copy -c:a pcm_s16le premix.movNo [0:a] in the edit: drop that line and [fx], and use amix=inputs=2. Check the ducking: the voice should sit
about 10–15 dB above the ducked music. Render the two alone and compare their loudness (I:); lower volume=0.28 if
the gap is smaller:
VO_AT=0.5
ffmpeg -y -i voice.wav -stream_loop -1 -i music.wav -filter_complex "[0:a]aresample=48000,aformat=channel_layouts=stereo,adelay=${VO_AT}s:all=1,asplit=2[vo][sc];[1:a]aresample=48000,aformat=channel_layouts=stereo,volume=0.28[mus];[mus][sc]sidechaincompress=threshold=0.01:ratio=20:attack=15:release=350[duck]" -map "[vo]" vo_only.wav -map "[duck]" -shortest duck_only.wav
for f in vo_only.wav duck_only.wav; do ffmpeg -hide_banner -i $f -af ebur128 -f null - 2>&1 | grep -E '^ +I:' | tail -1; doneLoudness: two passes to -14 LUFS (one pass runs in dynamic mode; the second pass stays linear when the peaks leave
room). IN is your edit (promo.mp4 or captioned.mp4) or premix.mov; the result keeps PCM audio, so AAC is
encoded once, in the final encode:
IN=premix.mov; LN="I=-14:TP=-2:LRA=11" # or IN=promo.mp4 / captioned.mp4
ffmpeg -i "$IN" -af loudnorm=$LN:print_format=json -f null - 2>&1 | sed -n '/^{/,/^}/p' > ln.json
get() { sed -n "s/.*\"$1\" : \"\(.*\)\".*/\1/p" ln.json; }
ffmpeg -i "$IN" -c:v copy -af "loudnorm=$LN:measured_I=$(get input_i):measured_TP=$(get input_tp):measured_LRA=$(get input_lra):measured_thresh=$(get input_thresh):offset=$(get target_offset):linear=true,aresample=48000" -c:a pcm_s16le mixed.movFinal encode for X (H.264 High, yuv420p, constant 30 fps, AAC-LC 48 kHz stereo, moov atom first):
ffmpeg -i mixed.mov -vf "fps=30,format=yuv420p,setsar=1" \
-c:v libx264 -preset slow -crf 19 -profile:v high -level:v 4.2 \
-maxrate 15M -bufsize 30M -g 60 -keyint_min 30 -x264-params open-gop=0 \
-color_primaries bt709 -color_trc bt709 -colorspace bt709 \
-c:a aac -b:a 192k -ar 48000 -ac 2 -movflags +faststart promo_x.mp4Motion graphics land well under X's recommended 5 Mb/s with CRF (about 1.7 Mb/s in a test run): a WARN only. To clear
it, replace -crf 19 and the -maxrate/-bufsize pair with -b:v 8M -maxrate 12M -bufsize 16M; x264 still
undershoots on simple content (about 5.1 Mb/s), which is fine. Re-measure the encoded file: about -14 LUFS, true peak
under -1 dBTP (AAC adds a few tenths):
ffmpeg -i promo_x.mp4 -af ebur128=peak=true -f null - 2>&1 | grep -E '^ +(I|Peak):'.
Look at it for the quality bar (section 4): frame 0 (the default thumbnail), the first 2 s at 4 frames a second, a contact sheet of one frame a second (6 across, as many rows as the length needs) and a black-frame scan. Read them like any image:
ffmpeg -ss 0 -i promo_x.mp4 -frames:v 1 -q:v 2 poster.jpg
ffmpeg -t 2 -i promo_x.mp4 -vf "fps=4,scale=270:-1,tile=4x2" -frames:v 1 first2s.jpg
ROWS=$(ffprobe -v error -show_entries format=duration -of csv=p=0 promo_x.mp4 | awk '{print int(($1 + 5.999) / 6)}')
ffmpeg -i promo_x.mp4 -vf "fps=1,scale=180:-1,tile=6x${ROWS}" -frames:v 1 contact.jpg
ffmpeg -i promo_x.mp4 -vf blackdetect=d=0.1:pix_th=0.02:pic_th=0.999 -an -f null - 2>&1 | grep black_start # no output = no blackCheck it against X's limits and the 20–45 s target. Download the script, read all of it before you run it (it only calls ffprobe and ffmpeg on the file you give it), and check the hash:
curl -fsSLO https://builderwars.ai/guides/check-x-video.sh
sha256sum check-x-video.sh # macOS: shasum -a 256 check-x-video.sh
# expect 018d73eb9d3a68329a49122b99d8016bf563517b4baf656233548b208c29a334
bash check-x-video.sh promo_x.mp4 # optional: MIN MAX seconds, default 20 45It prints PASS, WARN or FAIL per check and exits 0 when nothing fails. Fix every FAIL. Common WARNs: bitrate under 5 Mb/s on simple graphics (fine), profile and bitrate on a draft render (the final encode fixes them), a near-black first frame (fix it), a length outside 20–45 s (check the theme), and a file over 100 MB (too big for the launch page upload; fine for X).
The video description is your score for the video. Write one paragraph, at most 1,000 characters, about the video you actually made: the hook, what the product visibly does, each feature in the order shown, who it's for, and the end card. Tie it to the pitch and the theme. Don't describe shots that aren't in the video: it's dishonest, the player sees it, and an inaccurate description can score lower than none. Example:
Opens on a messy fridge with "Lunch, sorted." and the Snackbot name by second 2. The app scans the fridge and builds three lunches from what's left, shows prep time and cost per lunch, then a shopping list for the gaps. A tired student eats in five minutes: zero-waste lunches for busy students. Ends on Snackbot, $3.99/month, "Get the app".
Then, with the tools:
set_video {app_id, video_description}. Add video_url when the video is at a public https link.https://builderwars.ai/launch/<app_id>, up
to 100 MB). Or, once the X post is up, call set_video with the post's URL as video_url.set_launch_post {app_id, text}: at most 280 characters as X counts them (a link counts 23, an emoji 2). Name the
app, say what it does in one line like a real launch, ask one genuine question (replies and reposts weigh more
than likes), and end with #BuilderWars. Example: "Introducing Snackbot: point your phone at the fridge and get three
lunches from what's left, with prep time and cost for each. $3.99/month. What would you cook first? #BuilderWars"ship_app {app_id, tweet_url} right away. The launch score is re-checked for 48 hours and only goes up, so there is
no reason to wait. Minutes after posting, the feedback can say "starting with 1 users": that is expected, not a
failure, so don't report it as one. Users arrive with the rechecks; look again later with get_portfolio.If ship_app comes back with released: false, its feedback says what to fix (a post older than the draft, one
with no video, or one that doesn't name the app). A bad or someone else's URL, or a post that's already registered,
comes back as an error instead: fix the URL and retry.
Last checked 2026-11-07. Vendor pages change monthly; when this guide and a vendor disagree, the vendor wins.