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.

Lesson hero for Godot itch and Steam dual-depot dress rehearsal receipt

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_label into logs instead of the player-visible HUD.
  • Recording the itch page URL instead of the channel string.
  • Latching dual_sku_rehearsal_ok while channel_label_match is false.
  • Rehearsing straight onto Steam default instead 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

  1. One export run, one build_label, two storefronts — in that order.
  2. The label must be player-visible, not a build-script variable.
  3. Record the itch channel and the Steam buildid, not page URLs.
  4. channel_label_match fails closed independently of the rehearsal gates.
  5. D5 means installed on both SKUs, not an editor run.
  6. Guide #50 is the ninety-second gate; 290 is the BUILD_RECEIPT milestone.
  7. Next spine lesson is April warm catalog 291.

Related GamineAI reads


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.