Lesson 290: Godot itch and Steam Dual-Depot Dress Rehearsal Receipt on BUILD_RECEIPT (2026)
Direct answer: Before the October 2026 upload peak lets you push an itch build and a Steam depot from two different exports, promote dual_sku_dress_rehearsal_receipt_v1.json with D1–D6—export once from Godot, paint a single build_label into the player-visible HUD, push to an itch channel and a Steam beta branch, crosswalk butler channel metadata against the Steamworks buildid, smoke both installed SKUs, and fail-close dual_sku_rehearsal_ok plus channel_label_match on BUILD_RECEIPT. Pair Guide #50, the dual-depot evening, and Help #55 for mismatch recovery.

Why this matters now (October upload peak, two storefronts, one build)
Shipping to itch and Steam in the same week is normal in 2026, and that is exactly when dual-SKU drift starts. The usual failure is not a broken build — it is two builds that both work and disagree about which one players are testing. Marketing links the itch page, a bug report arrives from the Steam beta, and nobody can say whether the two are the same code.
Lesson 289 latched the localization freeze. 290 is the next spine latch: prove the two storefronts carry one rehearsed build before the October peak, when queue times are long and re-uploads are expensive.
Guide #50 is the ninety-second Godot export / HUD / butler gate; this lesson is the course-project BUILD_RECEIPT milestone plus verify script.
Beginner path (50-minute rehearsal)
| Step | Action | Success check |
|---|---|---|
| 1 | Pick one build_label for the rehearsal |
Written down before exporting |
| 2 | Export once from Godot (Web + desktop presets) | Both artifacts from the same export run |
| 3 | Paint build_label into boot HUD / About |
Visible without a debug console |
| 4 | Push to an itch channel with butler |
Channel name recorded |
| 5 | Push the same artifact set to a Steam beta branch | buildid recorded |
| 6 | Install both and screenshot the HUD label | Two screenshots, same string |
| 7 | File the receipt, run verify, latch BUILD_RECEIPT | Exit 0 |
Time: ~50 minutes for a first rehearsal; ~20 minutes once presets and the butler channel exist.
Prerequisites: Godot project with Web and desktop export presets; itch.io butler installed and authenticated; Steamworks partner access with a beta branch you can overwrite; jq + Bash/Git Bash; Guide #50 bookmarked.
Developer path (gates D1–D6)
| Gate | Check | Fail when |
|---|---|---|
| D1 | Single export run | itch and Steam artifacts built hours apart from different commits |
| D2 | build_label painted in HUD |
Label only in a build script variable, invisible to players |
| D3 | itch channel push recorded | butler push output discarded; channel guessed later |
| D4 | Steam buildid crosswalk |
Depot uploaded but buildid never written beside the channel |
| D5 | Both SKUs smoked installed | Editor run or web preview counted as proof |
| D6 | Receipt + verify | dual_sku_rehearsal_ok flipped before channel_label_match is true |
Cross-cutting: channel_label_match is a separate boolean from dual_sku_rehearsal_ok. The rehearsal can be complete (D1–D5 ran) while the labels still disagree — that combination must fail closed, not round up.
Cousin receipt crosswalk
| Field | Lesson 269 VDF LF | Dual-depot evening blog | This lesson (290) |
|---|---|---|---|
| Schema | vdf_line_ending_receipt_v1 |
dual_sku_dress_rehearsal_receipt_v1 (ritual) |
dual_sku_dress_rehearsal_receipt_v1 |
| Window | steamcmd parse before upload | One-evening D1–D6 | BUILD_RECEIPT latch |
| Pass field | vdf_lf_ok |
evening dual_sku_rehearsal_ok |
dual_sku_rehearsal_ok + channel_label_match |
Lesson 269 proves the Steam upload can parse. It says nothing about whether itch got the same build. Reference it in cousin_receipts; do not flatten the schemas.
D1–D2 — One export, one label
Choose the label first, then export — deriving it afterwards is how two artifacts end up with two labels.
LABEL="fest-demo-2026-10-rehearsal-$(git rev-parse --short HEAD)"
echo "$LABEL" > build/BUILD_LABEL
# Export both presets from the same checkout, same run
godot --headless --export-release "Web" build/web/index.html
godot --headless --export-release "Windows" build/win/game.exe
Read BUILD_LABEL at boot and draw it in the HUD or About screen (see the Godot export docs for bundling the file with the project). A label a player cannot read is not D2 evidence.
D3–D4 — Channel and buildid crosswalk
# itch: record the channel you actually pushed
butler push build/web itchuser/rpg-demo:html5-rehearsal
butler status itchuser/rpg-demo:html5-rehearsal > build/itch_channel_status.txt
# Steam: capture the buildid from the SteamPipe output / Steamworks builds page
# (see https://partner.steamgames.com/doc/sdk/uploading)
Then write both identifiers into the receipt beside the same label:
| Field | Example | Rule |
|---|---|---|
itch.channel |
html5-rehearsal |
Exact channel string, not the page URL |
itch.build_label |
fest-demo-2026-10-rehearsal-a1b2c3d |
From the HUD, not the build script |
steam.branch |
rehearsal |
Beta branch, never default for a rehearsal |
steam.buildid |
19482013 |
From Steamworks, recorded at push time |
steam.build_label |
same string as itch.build_label |
Any difference ⇒ channel_label_match: false |
D5 — Both SKUs installed, not previewed
Install the itch build through the itch app (or a real browser load for HTML5) and install the Steam beta branch. Screenshot the HUD label on each. Save into release-evidence/dual-sku/2026-10/smoke/. A Godot editor run proves nothing about what the depot contains.
D6 — Receipt + verify + BUILD_RECEIPT
{
"schema": "dual_sku_dress_rehearsal_receipt_v1",
"rehearsal_label": "october-upload-peak-2026",
"rehearsed_utc": "2026-10-04T17:00:00Z",
"build_label": "fest-demo-2026-10-rehearsal-a1b2c3d",
"export_run": {
"commit": "a1b2c3d",
"single_run": true,
"presets": ["Web", "Windows"]
},
"itch": {
"channel": "html5-rehearsal",
"build_label": "fest-demo-2026-10-rehearsal-a1b2c3d",
"status_path": "release-evidence/dual-sku/2026-10/itch_channel_status.txt"
},
"steam": {
"branch": "rehearsal",
"buildid": "19482013",
"build_label": "fest-demo-2026-10-rehearsal-a1b2c3d"
},
"smoke_dir": "release-evidence/dual-sku/2026-10/smoke",
"cousin_receipts": {
"vdf_lf_lesson_269": "release-evidence/steam/VDF_LINE_ENDING_RECEIPT.json"
},
"gates": {
"D1_single_export": "pass",
"D2_hud_label": "pass",
"D3_itch_channel": "pass",
"D4_steam_buildid": "pass",
"D5_installed_smoke": "pass",
"D6_receipt": "pass"
},
"channel_label_match": true,
"dual_sku_rehearsal_ok": true,
"notes": "Rehearsal only — production setlive still requires the fest-day depot audit"
}
Pin at release-evidence/dual-sku/2026-10/DUAL_SKU_DRESS_REHEARSAL_RECEIPT.json.
./scripts/verify_dual_sku_dress_rehearsal.sh \
release-evidence/dual-sku/2026-10/DUAL_SKU_DRESS_REHEARSAL_RECEIPT.json
Minimum verify sketch:
#!/usr/bin/env bash
set -euo pipefail
REC="${1:-release-evidence/dual-sku/2026-10/DUAL_SKU_DRESS_REHEARSAL_RECEIPT.json}"
jq -e '.schema == "dual_sku_dress_rehearsal_receipt_v1"' "$REC"
jq -e '.export_run.single_run == true' "$REC"
jq -e '.itch.build_label == .steam.build_label' "$REC"
jq -e '.itch.build_label == .build_label' "$REC"
jq -e '.channel_label_match == true' "$REC"
jq -e '.dual_sku_rehearsal_ok == true' "$REC"
test -d "$(jq -r '.smoke_dir' "$REC")"
echo "dual_sku_dress_rehearsal verify: OK"
BUILD_RECEIPT latch:
{
"dual_sku_rehearsal_ok": true,
"channel_label_match": true,
"dual_sku_dress_rehearsal_receipt": "release-evidence/dual-sku/2026-10/DUAL_SKU_DRESS_REHEARSAL_RECEIPT.json"
}
Relationship to Guide / evening / Help / cousins
| Artifact | Role |
|---|---|
| Guide #50 | Ninety-second Godot export / HUD / butler gate |
| Dual-depot evening blog | Full D1–D6 ritual depth |
| This lesson | BUILD_RECEIPT milestone |
| Lesson 269 | steamcmd parse cousin (not dual-SKU) |
| Help #55 | Channel label mismatch recovery |
| Wishlist-split case study | What drift costs in wishlists |
Order: VDF LF green (269) → Guide #50 D1–D6 → this lesson latch → April warm catalog Lesson 291. If the labels already disagree on live channels, run Help #55 before flipping OK again.
Common mistakes
- Exporting itch and Steam artifacts from two different commits "because only the web build changed."
- Painting
build_labelinto logs instead of the player-visible HUD. - Recording the itch page URL instead of the channel string.
- Latching
dual_sku_rehearsal_okwhilechannel_label_matchis false. - Rehearsing straight onto Steam
defaultinstead of a beta branch. - Counting a Godot editor run or an unpublished web preview as D5.
- Assuming Lesson 269 parse-green means the two storefronts agree.
Troubleshooting
| Symptom | Fix |
|---|---|
| HUD labels differ between SKUs | Help #55 — re-export once, re-push both, refile |
butler push succeeded but channel unknown |
Re-run butler status <user>/<game>:<channel> and store the output path |
Steam buildid missing from receipt |
Read it from the Steamworks builds page; do not backfill from memory |
| steamcmd rejects the VDF before you get here | Lesson 269 / VDF LF gate first |
| Wishlists split across storefronts | Dual-SKU wishlist-split case study |
| Unsure whether itch should be free or paid | itch PWYW vs Steam demo SKU economics |
Arc continuity (289–294 high spine)
| Lesson | Milestone |
|---|---|
| 289 | Ren'Py localization string freeze |
| 290 (this) | Godot itch/Steam dual-depot dress rehearsal |
| 291 (queued) | Unity April Addressables warm catalog |
| 292 (queued) | Photoshop Summer Sale capsule sticker |
| 293 (queued) | Construct soft-launch wishlist window |
| 294 (queued) | GameMaker IARC age-rating packet |
Previous: Lesson 289 — Ren'Py localization string freeze Next: Lesson 291 — Unity April Addressables warm catalog (queued)
FAQ
Does the rehearsal have to use a beta branch?
Yes. Rehearsing on default means a mistake is live. Use a branch you can overwrite freely.
Can itch and Steam ship different content later? They can — but then it is no longer one rehearsed build, and you need a new label plus a fresh receipt. Do not silently reuse a green one.
Why is channel_label_match separate from dual_sku_rehearsal_ok?
Because you can complete every step and still ship mismatched labels. Splitting the booleans makes that failure visible instead of averaged away.
Is Help #55 required first? Only when labels already disagree on live channels — prevention is Guide #50 plus this lesson.
Key takeaways
- One export run, one
build_label, two storefronts — in that order. - The label must be player-visible, not a build-script variable.
- Record the itch channel and the Steam buildid, not page URLs.
channel_label_matchfails closed independently of the rehearsal gates.- D5 means installed on both SKUs, not an editor run.
- Guide #50 is the ninety-second gate; 290 is the BUILD_RECEIPT milestone.
- Next spine lesson is April warm catalog 291.
Related GamineAI reads
- Godot dual-depot dress rehearsal preflight (Guide #50)
- Your first itch/Steam dual-depot dress rehearsal evening
- Dual-depot channel label mismatch (Help #55)
- How to use itch butler in the app
- itch PWYW vs Steam demo SKU economics worksheet
- Official: itch.io butler · Steamworks SteamPipe · Godot exporting projects
Two storefronts with two labels is one bug report you cannot answer—export once, paint the label where players can read it, crosswalk the itch channel against the Steam buildid, smoke both installed, then latch dual_sku_rehearsal_ok only when channel_label_match is honestly true.