Ship the game — export, build & release (per engine · per platform)

SkillWeb & browsing

Use when exporting, packaging, signing, or publishing a game — export presets per engine, macOS notarization, cross-origin isolation for threaded web builds, Android AAB/keystore, iOS provisioning, store submission (Steam, Play, App Store, itch.io), headless build CI. NOT gameplay code inside the artifact (that is `godot`, `unity`, `unreal`), NOT hosting a web app or container (that is `deployment`).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Ship the game — export, build & release (per engine · per platform) skill

What this skill tells your AI

The instructions your AI receives, as published by ericrisco/rsc-harness in skills/gamedev-shipping/SKILL.md and read by ahel’s review.

Take a finished project from editor → signed, correctly-configured artifact → live on the right target (desktop / web / mobile / store), with the platform gotchas that silently break a build handled up front. This skill owns the packaging and release boundary; the engine skills own the code that runs inside the artifact.

project ──[engine export/build]──▶ artifact ──[per-platform sign/config]──▶ target
Godot templates·Unity Build Profiles·Unreal Shipping    desktop·web·mobile    Steam·Play·App Store·itch

Version anchors (what "current" means here — July 2026)

ToolTargetShip note
Godot4.5 stable (4.6 dev)Export templates must match the editor build exactly (4.5.1 editor ⇒ 4.5.1 templates). C#/.NET edition has NO web export — GDScript/standard only.
UnityUnity 6 (6000.x)"Build Settings" is now Build Profiles. IL2CPP backend for iOS/Android/WebGL.
Unreal5.6Ship in the Shipping config; test packaged builds in Development.
AndroidJDK 17, target a recent API levelAAB required for Google Play (new apps + updates). APK only for sideload/itch/direct.
iOS/macOSXcode 16+, notarytoolaltool notarization is dead — use xcrun notarytool.

When NOT to use — and who owns it instead

AskOwnerWhy the line is there
Gameplay, shaders, netcode, physics — any runtime logic../godot/SKILL.md, ../unity/SKILL.md, ../unreal/SKILL.md, ../gamedev-shaders/SKILL.md, ../gamedev-multiplayer/SKILL.md, ../gamedev-physics/SKILL.mdThis skill packages code, it does not write it. The boundary is the export button.
Hosting a normal web app, API or container (Dockerfile, Coolify, Vercel, VPS)../deployment/SKILL.mdA WebAssembly game is static files; only its headers are special (below). Borrow its Actions + header-setting patterns.
Game feel, economy, narrative../game-design/SKILL.md, ../game-storytelling/SKILL.mdDesign intent, not packaging.
Signing-key and CI secret handling in general../secure-coding/SKILL.mdThe practice this skill assumes for keystores/certs.

Decision rules (settle these before touching a build)

QuestionRule
Web build uses threads / SharedArrayBuffer?You must serve COOP: same-origin + COEP: require-corp (cross-origin isolation). No headers ⇒ no threads ⇒ boot failure.
Godot + C# + web?Impossible today. Switch to GDScript for web, or ship that title desktop/mobile only.
Play Store?AAB, signed with an upload key, Play App Signing on. Not APK.
macOS distribution outside the App Store?codesign (Developer ID) → notarizestaple. Skipping = Gatekeeper blocks it.
Mac App Store?Different cert (Apple Distribution) + sandbox entitlements; notarization is automatic, not manual.
Steam?SteamPipe upload via steamcmd + an app_build VDF; set the default branch live in the partner site.
Quick public build / demo / jam?itch.io + butler push. Tick "SharedArrayBuffer support" for threaded web builds.
Release config?Godot release preset (debug off), Unity release/IL2CPP, Unreal Shipping. Never ship a debug/Development build.

Export / build per engine

GodotEditor ▸ Manage Export Templates (match version), then Project ▸ Export, add one preset per platform. Headless for CI:

godot --headless --export-release "Windows Desktop" build/game.exe
godot --headless --export-release "Web"             build/index.html
# Android AAB needs the Gradle build template + JDK 17 + a release keystore (see references)

Web preset toggles: Thread Support (⇒ needs COOP/COEP) and Extensions Support. Android needs Project ▸ Install Android Build Template (enables Use Gradle Build).

UnityFile ▸ Build Profiles (formerly Build Settings). Create a profile per platform; override Player Settings per profile (company/product name, bundle id, icons, scripting backend). iOS/Android/WebGL force IL2CPP. CI headless:

Unity -quit -batchmode -nographics -projectPath . \
  -buildTarget Android -executeMethod BuildScript.PerformBuild -logFile -

Android: tick Build App Bundle (Google Play) for AAB; set the keystore in Publishing Settings. WebGL: threads ⇒ COOP/COEP; keep decompression fallback for hosts that can't set gzip/br headers.

Unreal — build configurations, not "modes": Debug, DebugGame, Development (test packaged builds — keeps logs/console), Test, Shipping (release — strips logging, most optimized). Package via Platforms ▸ <target> ▸ Package Project, the Project Launcher, or UAT for CI:

RunUAT BuildCookRun -project="Game.uproject" -platform=Win64 \
  -clientconfig=Shipping -cook -stage -pak -package -archivedirectory=build

Android adds -distribution for an AAB; iOS requires a Mac + provisioning. Run the platform Turnkey/SDK setup once (Android Studio + NDK, or Xcode) or the cook fails.

→ full per-engine preset/flag tables: references/mobile-and-desktop.md and references/stores-and-ci.md.

Per-platform

Desktop (Windows / macOS / Linux) — the simplest path

OSSign?Gotcha
WindowsOptional (Authenticode; EV avoids SmartScreen warm-up)Unsigned still runs but SmartScreen warns; ship .exe + engine data next to it, or an installer.
LinuxNoShip binary + data, or wrap as AppImage/Flatpak for a portable single file.
macOSRequiredMust codesign (Developer ID Application) + notarize + staple, or Gatekeeper refuses to open it.

macOS notarization (the step everyone forgets):

codesign --deep --force --timestamp --options runtime \
  --sign "Developer ID Application: Studio (TEAMID)" Game.app
ditto -c -k --keepParent Game.app Game.zip
xcrun notarytool submit Game.zip --keychain-profile "AC" --wait   # store creds once via store-credentials
xcrun stapler staple Game.app                                     # staple so it verifies offline

--options runtime (Hardened Runtime) is mandatory for notarization. Codesign every bundled .dylib/helper before the outer .app, inside-out.

Web — the header trap (read this before blaming your build)

Threaded WebAssembly builds (Godot with Thread Support, Unity WebGL with threads) use SharedArrayBuffer, which browsers gate behind cross-origin isolation. The server must send:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Missing them ⇒ SharedArrayBuffer is not defined, a hang, or a blank canvas — even though the files are fine. Where to set them:

  • Netlify _headers, Vercel vercel.json headers, nginx add_header — set both on the game path.
  • itch.io — tick "This file will be played in the browser" ▸ SharedArrayBuffer support; itch sets the headers for you.
  • GitHub Pagescannot set headers. Use a non-threaded export, or the coi-serviceworker shim (a service worker that re-serves with the headers).

COEP: require-corp also means every cross-origin subresource (fonts, analytics, embeds) must send CORP/CORS or it's blocked. Godot C#/.NET has no web export at all — use GDScript.

→ per-host configs, the service-worker shim, COEP subresource fallout, a self-test snippet and the Godot vs Unity web specifics: references/web-export-headers.md.

Mobile

Android — needs JDK 17 and the engine's Gradle build template. Ship AAB to Play (Google re-signs and generates per-device APKs), APK for sideload/itch/direct. Generate a release keystore once and reuse it forever (lose it = you can't update the app without Play App Signing key reset):

keytool -genkey -v -keystore release.keystore -alias game \
  -keyalg RSA -keysize 2048 -validity 10000

Keep the keystore + passwords out of git (CI secret). versionCode must increase every upload.

iOS — Mac + Xcode + Apple Developer account. Bundle ID must match an App ID; pick a provisioning profile (development / ad-hoc / App Store) and a signing certificate. Archive, then upload to App Store Connect / TestFlight via Xcode Organizer or Transporter. CFBundleVersion must increase per upload.

→ full keystore + Play App Signing, provisioning/TestFlight, Windows Authenticode and Linux AppImage/Flatpak walkthroughs: references/mobile-and-desktop.md.

Store prep

  • Assets: platform icons (Android adaptive, iOS icon set, Windows .ico, macOS .icns), splash/launch, store screenshots per device class, a trailer where required.
  • Metadata: title, short + full description, keywords, category, support/privacy URLs, localized strings.
  • Age ratings: fill the IARC questionnaire (Play, Microsoft, Nintendo) or ESRB/PEGI/USK; Apple has its own age-rating questionnaire. Wrong answers = removal.
  • Google Play: AAB + Play App Signing, a recent target API level, content rating, and the Data safety form.
  • Steam: Steamworks partner + appid; define depots; upload with SteamPipe (steamcmd +run_app_build app_build_APPID.vdf); store page assets; set the branch live.
  • itch.io: butler push build/ user/game:channel --userversion 1.2.3 — channel name (e.g. windows, html5) picks the platform.
  • Mac App Store: Apple Distribution cert + App Sandbox entitlements (not Developer ID); notarization is handled by App Store submission.

→ Steamworks/SteamPipe VDF, butler channels, Play/App Store submission checklists: references/stores-and-ci.md.

CI/CD for builds

Headless export in GitHub Actions; version from the run/commit so every artifact is traceable.

  • Godot: run in a container that bundles matching export templates (e.g. a barichello/godot-ci-style image), godot --headless --export-release.
  • Unity: game-ci/unity-builder + a license activation step (secrets); matrix over targets.
  • Unreal: needs a self-hosted runner with the engine installed (multi-GB); UAT BuildCookRun.
  • Signing in CI: import the keystore/cert from an encrypted secret at runtime, never commit it. macOS notarization runs on a macos-latest runner with notarytool + an App Store Connect API key.
  • Versioning: semver tag for releases; a monotonic build number from github.run_number or the git commit count feeds versionCode/CFBundleVersion/Steam build description.

→ ready per-engine workflows with signing: references/stores-and-ci.md.

Ship-time optimization

  • Texture compression per platform: ASTC (mobile), BCn/DXT (desktop), ETC2 (GLES fallback). Set per-platform import overrides — never ship uncompressed PNGs to mobile.
  • Strip the fat: Unity Managed Stripping Level: High + IL2CPP; Unreal Shipping drops logs/console; Godot — export only used resources and disable unused modules in a custom template.
  • Startup: small boot scene, stream/preload heavy assets, compress the .pck/data, use Ogg Vorbis over WAV. Web: enable gzip/brotli on .wasm/.pck.
  • Budgets: set a per-platform size budget (web especially — every MB is download latency) and check the final artifact against it before submitting.

Anti-patterns

Anti-patternDo instead
Web build works locally, host it laterSet COOP/COEP now; file:// and simple servers hide the SharedArrayBuffer failure.
Planning a Godot C# web exportIt doesn't exist in 4.x. GDScript for web, or no web.
Treating notarization as optionalOn macOS, no notarize + staple = Gatekeeper blocks the app for every user.
Shipping an APK to the Play StorePlay requires AAB for new apps and updates.
Committing the keystore so CI can signKeystore/cert live in encrypted CI secrets, never in git.
Shipping the Development/Debug configShip Godot release / Unity IL2CPP release / Unreal Shipping — debug leaks console + is slow.
Export templates "close enough" (4.5 vs 4.5.1)Godot templates must match the editor build exactly or export breaks.
Uncompressed texturesPer-platform compression (ASTC/BCn/ETC2) — raw textures blow memory + size.
Reusing an old versionCode/CFBundleVersionBoth must strictly increase or the store rejects the upload.

Checklist

  • Release/Shipping config, not Debug/Development.
  • (Godot) export templates match the editor version exactly.
  • (Web, threaded) COOP same-origin + COEP require-corp served; boots without SharedArrayBuffer errors.
  • (Godot + web) confirmed GDScript, not C#/.NET.
  • (macOS) codesigned (Hardened Runtime) + notarized + stapled.
  • (Android) AAB for Play, JDK 17, release keystore in CI secrets, versionCode bumped.
  • (iOS) provisioning profile + signing cert correct, CFBundleVersion bumped.
  • Store assets: icons, splash, screenshots, metadata, age rating done.
  • Version/build number set from CI and traceable.
  • Textures compressed per platform; build size within budget.

Signals

GitHub stars
82
Forks
3
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
gamedev-shipping
Source
github.com/ericrisco/rsc-harness