Your First itch.io + Steam Dual-Depot Dress Rehearsal Before October Upload Peak - One Evening - 2026

September 2026 is when micro-studios drip an itch.io HTML5 or browser demo while Steam Next Fest upload queues tighten. Marketing posts the itch “play now” link on Tuesday. Steam still serves last week’s depot. Your BUILD_RECEIPT.json lists one build_id in the header but the itch row still says 2026-09-04-rc01 while Steam shows 2026-09-11-rc02. Thursday promotion looks GREEN on smoke logs—and RED on wishlist math because players tried two different builds on two storefronts.
This tutorial is the dress rehearsal before that week. Not economics (see the itch PWYW vs Steam demo worksheet). Not recovery after wishlists split (forward blog #5 case study). Not Thursday’s ten-minute row diff ritual (that consumes this receipt). This URL owns the one-evening proof that itch and Steam channels agree on build_id, build_label, and channel_label_match before October upload peak.
Direct answer: spend one evening creating dual_sku_dress_rehearsal_receipt_v1.json. Run gates D1–D6. Document a dual-channel matrix, pin the same build_id on both surfaces, smoke itch browser + Steam client, set BUILD_RECEIPT.dual_sku_rehearsal_ok: true only when every gate passes.
Pair early with BUILD_RECEIPT beginner pipeline if receipts are new, and with localization string freeze evening when fest locales must match both storefronts.
Why this matters now
Dual-SKU distribution is default for 2026 PC indies, but ops evidence still lags.
- itch “instant play” hides version drift. Browser caches and Butler uploads can look successful while players fetch an older WASM bundle—see itch WASM MIME help when MIME is right but bytes are stale.
- Steam depot IDs are not your build_id. Steamworks build numbers and your receipt
build_idmust crosswalk explicitly or Thursday row review fails R6. - October upload peak punishes late discovery. Finding channel mismatch during queue week costs a promotion day you cannot buy back.
channel_label_matchis a promotion latch. Thursday BUILD_RECEIPT row review treats falsechannel_label_matchas RED—this rehearsal prevents that surprise.- One evening is enough when scope stays narrow. One candidate build, two channels, one matrix, two smokes, one JSON receipt.
Official references: Steamworks Builds documentation, itch.io Butler documentation, and your engine export docs for HTML5 vs standalone targets.
Who this one-evening ritual is for
| Reader | What you should leave with |
|---|---|
| First-time dual-SKU team | A channel matrix you can re-run before every fest promotion |
| Solo developer | Proof that itch and Steam reference the same build_id |
| Release producer | D1–D6 decision table with fail actions before upload week |
| Marketing lead | Honest answer when asked “is the itch demo the same build as Steam?” |
| Build engineer | Crosswalk table linking depot id, Butler version, and receipt rows |
Time: 90–120 minutes first pass; 40–55 minutes once scripts and matrix template exist.
Prerequisites:
- Live or staging itch page with HTML5 or downloadable demo lane.
- Steam app with fest demo branch or playtest depot configured.
- Existing
BUILD_RECEIPT.jsonhabit (beginner pipeline). - Candidate build exported for both targets (or documented why surfaces differ with exception ticket).
- Browser for itch smoke; Steam client for depot smoke.
- Optional:
localization_freeze_okfrom freeze evening when both SKUs claim the same locales.
Format ladder - where this article fits
| Format | Job | Boundary |
|---|---|---|
| This tutorial | One-evening dual-depot dress rehearsal with D1–D6 | Owns dual_sku_rehearsal_ok |
| BUILD_RECEIPT beginner | Root receipt + upload log habit | Owns first receipt schema |
| Thursday row review | Weekly promotion diff R1–R10 | Consumes channel_label_match from this rehearsal |
| Wednesday demo smoke | Binary health before promotion | Owns smoke gates, not channel matrix |
| itch PWYW economics | Runway and cost lanes | Owns money math, not depot proof |
| Forward #5 case study | Recover wishlist split after itch diluted Steam | Owns recovery R1–R6, not prevention |
| Guide #50 / Help #55 / Resource #53 | One-step fixes and tool lists | Link here once published |
Non-repetition note: Thursday row review owns weekly R-gates and build_receipt_row_diff_receipt_v1.json. Economics worksheet owns subsidy and runway. Localization freeze owns L1–L6. This URL owns pre-October dual-channel dress rehearsal with D1–D6 and dual_sku_dress_rehearsal_ok.
Gates D1–D6
| Gate | Question | Required evidence | Fail action |
|---|---|---|---|
| D1 | Is one candidate build_id declared for this rehearsal? |
Single build_id string in receipt + git commit crosswalk |
Stop. Pick one candidate; never rehearse two builds silently. |
| D2 | Are both channels documented in channels[]? |
itch row + Steam row with surface, URL, upload UTC | Add missing channel or document defer with exception. |
| D3 | Does channel_label_match hold across channels? |
Same build_label / VERSION token on itch page, in-game HUD, Steam description notes |
Align labels or file numbered exception in receipt notes. |
| D4 | Can you prove each channel serves the candidate bytes? | itch etag or Butler version + Steam buildid or manifest hash |
Re-upload stale channel; purge CDN if needed. |
| D5 | Does smoke pass on itch browser and Steam client? | Screenshots + short logs per surface | Fix blocking bug; do not set dual_sku_rehearsal_ok. |
| D6 | Is evidence filed and BUILD_RECEIPT updated? | dual_sku_dress_rehearsal_receipt_v1.json + dual_sku_rehearsal_ok: true |
Block October promotion until complete. |
Gates are PASS, FAIL, or NOT_RUN only.
Evening overview - four blocks
| Block | Minutes | Output |
|---|---|---|
| 1 — Declare candidate build_id | 15 | build_id + git commit in working notes |
| 2 — Author channel matrix | 25 | channels[] with itch + Steam rows |
| 3 — Label match + byte proof | 30 | D3 table + D4 hashes / buildids |
| 4 — Dual smoke + receipt | 40–50 | D5 artifacts + dual_sku_rehearsal_ok |
Evidence folder:
release-evidence/
dual-sku/
2026-09-12-october-rehearsal/
dual_sku_dress_rehearsal_receipt_v1.json
channel_matrix.md
smoke/
itch-main-menu.png
steam-main-menu.png
itch-smoke.log
steam-smoke.log
Block 1 - declare one candidate build_id (D1)
Pick the build you intend to promote during October prep—not “latest main” and not “whatever itch cached.”
{
"build_id": "studio_game_fest_2026-09-12_a1b2c3d4",
"git": {
"commit": "a1b2c3d4e5f678901234567890abcdef12345678",
"branch": "fest/october-demo",
"dirty": false
},
"rehearsal_utc": "2026-09-12T17:00:00Z"
}
Naming should follow the same discipline as partner ZIP naming: studio, game, channel context, short hash, UTC date.
Beginner rule: if itch and Steam were built from different commits tonight, you are not ready for D1 PASS—either rebuild both from one commit or document a numbered exception with expiry (still FAIL for promotion until resolved).
D1 passes when exactly one build_id owns the rehearsal.
Block 2 - channel matrix (D2)
Document both surfaces in channels[]—not a vague “we are on itch and Steam.”
{
"channels": [
{
"surface": "itch_html5",
"url": "https://your-name.itch.io/your-game",
"play_url": "https://your-name.itch.io/your-game/html5/index.html",
"upload_tool": "butler",
"butler_version": "15.21.0",
"uploaded_utc": "2026-09-12T16:10:00Z",
"build_label": "fest_2026-09-12_a1b2c3d4"
},
{
"surface": "steam_depot",
"app_id": "1234560",
"depot_id": "1234561",
"branch": "fest_public",
"steam_build_id": "9876543210",
"uploaded_utc": "2026-09-12T16:45:00Z",
"build_label": "fest_2026-09-12_a1b2c3d4"
}
]
}
Add a human-readable channel_matrix.md for producers:
| Surface | Player entry | Owner | Last upload UTC |
|---|---|---|---|
| itch HTML5 | itch page → Play in browser | @build |
2026-09-12T16:10:00Z |
| Steam demo | Store → Download demo | @release |
2026-09-12T16:45:00Z |
Append two rows to upload_log.csv per BUILD_RECEIPT beginner—one per portal, same build_id column.
D2 passes when both fest-facing channels exist with URLs, upload timestamps, and owners.
Block 3 - channel_label_match and byte proof (D3 + D4)
D3 - labels players and receipts can see
channel_label_match means the same build_label token appears:
- On the itch project page or in-browser VERSION file (if you ship one)
- In-game on a player-visible label per visible build label opinion
- In Steam fest branch notes or demo description footnote your team actually maintains
{
"channel_label_match": true,
"build_label": "fest_2026-09-12_a1b2c3d4",
"label_locations": [
"itch: project devlog footer",
"in_game: title screen lower-left",
"steam: fest branch README in depot notes"
]
}
If labels intentionally differ (e.g., itch HTML5 omits HUD label), set channel_label_match: false and add label_exception_id: EXC-001 with owner and expiry—Thursday row review will stay RED until resolved.
D4 - prove bytes, not just labels
itch HTML5
curl -sI "https://your-name.itch.io/your-game/html5/game.wasm" | grep -i etag
Record etag, last-modified, and optionally SHA-256 of downloaded game.wasm after hard refresh (Ctrl+Shift+R). Stale CDN edges are a dress-rehearsal failure, not a surprise on fest day—see itch WASM MIME cache help.
Steam depot
Record steam_build_id from Steamworks → Builds for branch fest_public. Crosswalk to your zip SHA-256 from ServerData or standalone export folder.
{
"byte_proof": {
"itch": {
"wasm_url": "https://your-name.itch.io/your-game/html5/game.wasm",
"etag": "\"abc123\"",
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
},
"steam": {
"steam_build_id": "9876543210",
"primary_zip_sha256": "c0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1"
}
}
}
D3 passes when channel_label_match is true or a documented exception exists. D4 passes when both proofs reference the candidate tied to D1’s build_id.
Block 4 - dual smoke and BUILD_RECEIPT (D5 + D6)
D5 - smoke both surfaces
Minimum matrix:
| Check | itch browser | Steam client |
|---|---|---|
| Reach main menu / first playable state | required | required |
Visible build_label matches D3 |
required | required |
| No blocking console error on boot | required | required |
| Screenshot filed | required | required |
itch smoke log snippet:
[surface] itch_html5
[build_label] fest_2026-09-12_a1b2c3d4
[wasm_etag] "abc123"
[result] main_menu_ok
Steam smoke log snippet:
[surface] steam_depot fest_public
[steam_build_id] 9876543210
[build_label] fest_2026-09-12_a1b2c3d4
[result] main_menu_ok
If you run Wednesday demo smoke the same week, link its JSON path in the receipt—dress rehearsal proves channels agree; Wednesday proves binary health.
D6 - file receipt and latch BUILD_RECEIPT
{
"schema": "dual_sku_dress_rehearsal_receipt_v1",
"build_id": "studio_game_fest_2026-09-12_a1b2c3d4",
"rehearsal_utc": "2026-09-12T19:30:00Z",
"channels": [],
"channel_label_match": true,
"byte_proof": {},
"smoke_dir": "release-evidence/dual-sku/2026-09-12-october-rehearsal/smoke",
"gates": {
"D1": "PASS",
"D2": "PASS",
"D3": "PASS",
"D4": "PASS",
"D5": "PASS",
"D6": "PASS"
},
"dual_sku_rehearsal_ok": true,
"localization_freeze_receipt": "release-evidence/localization/2026-09-12-october-fest-freeze/localization_freeze_receipt_v1.json",
"notes": "Forward blog #5 if wishlist split detected post-launch."
}
Crosswalk root receipt:
{
"dual_sku_rehearsal_ok": true,
"dual_sku_dress_rehearsal_receipt": "release-evidence/dual-sku/2026-09-12-october-rehearsal/dual_sku_dress_rehearsal_receipt_v1.json",
"channel_label_match": true,
"channels": [
{ "surface": "itch_html5", "build_label": "fest_2026-09-12_a1b2c3d4" },
{ "surface": "steam_depot", "build_label": "fest_2026-09-12_a1b2c3d4" }
]
}
D6 passes when the receipt is on disk, all gates are PASS, and BUILD_RECEIPT pointers resolve.
verify_dual_sku_dress_rehearsal.sh sketch
#!/usr/bin/env bash
set -euo pipefail
RECEIPT="${1:-release-evidence/dual-sku/latest/dual_sku_dress_rehearsal_receipt_v1.json}"
test "$(jq -r .dual_sku_rehearsal_ok "$RECEIPT")" = "true"
test "$(jq -r .channel_label_match "$RECEIPT")" = "true"
for g in D1 D2 D3 D4 D5 D6; do
test "$(jq -r ".gates.$g" "$RECEIPT")" = "PASS"
done
channels=$(jq '.channels | length' "$RECEIPT")
test "$channels" -ge 2
echo "dual_sku_rehearsal_ok"
Proof table
| Artifact | Proves | Does not prove |
|---|---|---|
build_id |
One candidate owns the rehearsal | Wishlist conversion |
channels[] |
Both surfaces documented | Marketing copy quality |
channel_label_match |
Labels agree across surfaces | Identical binaries if scopes differ by design |
| Byte proof rows | Stale CDN / wrong depot caught | Long-term drift after rehearsal |
| Dual smoke | Player-visible boot on both | Every gameplay path |
dual_sku_rehearsal_ok |
Promotion may proceed to Thursday diff | Economics viability |
Scenarios A–G
A - itch updated Monday, Steam still on last week
Symptom: D4 steam build_id older than git commit.
Action: FAIL D4; upload Steam before setting rehearsal ok.
B - Different build_label on itch footer vs HUD
Symptom: D3 false while marketers edited footer only.
Action: Align labels or fail promotion until EXC ticket resolves.
C - HTML5 scope smaller than Steam demo
Symptom: Legitimate content difference.
Action: Document scope table in receipt notes; keep channel_label_match true for version token, not feature parity claim.
D - WASM etag unchanged after Butler push
Symptom: itch smoke passes locally but etag stale.
Action: FAIL D4; follow MIME/cache help; hard-refresh smoke.
E - Steam branch typo fest_pubic
Symptom: Smoke hits wrong branch.
Action: FAIL D5; fix branch name in matrix and Steamworks.
F - No BUILD_RECEIPT yet
Symptom: D6 cannot latch.
Action: Run beginner pipeline first.
G - Wishlists drop after itch push
Symptom: Rehearsal was green; funnel split post-launch.
Action: Forward to blog #5 recovery case study—not a rehearsal pass issue.
Common mistakes
- Rehearsing two
build_idvalues without documenting scope split. - Checking itch only in editor export preview, not public URL.
- Treating Steam
buildidas optional metadata. - Setting
channel_label_match: truewhen only Steam has a visible label. - Skipping
upload_log.csvsecond row for itch. - Running dress rehearsal only once in August and assuming October still matches.
- Confusing this with dual-SKU economics—money vs bytes.
- Promoting on Thursday without linking rehearsal receipt path in row diff notes.
Troubleshooting
“Butler says success but players see old build.” Inspect etag and WASM SHA-256; purge browser cache; verify Butler pushed to the same itch channel you document in D2.
“Steam buildid does not match our zip hash.” Confirm you uploaded the zip tied to D1’s commit, not an older artifact from Downloads.
“itch is HTML5, Steam is Win64—must hashes match?” No. D4 proves each channel serves its candidate artifact for the same build_id intent. Document scope in notes.
“We only ship Steam for fest.” This rehearsal is optional—but if itch page still links “play in browser,” either remove the link or include itch in D2.
“GX.games is our third channel.” Extend channels[]; see triple-channel label match help when three HTML5 surfaces exist.
Printable checklist card (pin in release-evidence/dual-sku/)
[ ] D1 — One build_id + git commit recorded for tonight's candidate
[ ] D2 — channels[] has itch_html5 row AND steam_depot row with URLs
[ ] D2 — upload_log.csv has TWO rows (itch + Steam) sharing build_id
[ ] D3 — build_label identical on itch page, in-game HUD, Steam notes
[ ] D3 — channel_label_match true OR numbered EXC ticket with expiry
[ ] D4 — itch wasm etag / sha256 captured after hard refresh
[ ] D4 — steam_build_id matches Steamworks fest_public branch
[ ] D5 — itch browser smoke screenshot saved to smoke/
[ ] D5 — Steam client smoke screenshot saved to smoke/
[ ] D5 — Both smokes show same build_label token
[ ] D6 — dual_sku_dress_rehearsal_receipt_v1.json filed on disk
[ ] D6 — BUILD_RECEIPT.dual_sku_rehearsal_ok set only when D1–D6 PASS
[ ] Optional — localization_freeze_receipt path linked when locales claimed
Read the card aloud once before telling marketing the itch link is safe. Verbalizing catches “Steam uploaded yesterday, itch still loading” faster than assuming Butler green means players agree.
Crosswalk to Thursday row review (R6)
When Thursday row review runs the same week, copy these fields into build_receipt_row_diff_receipt_v1.json notes so R6 does not re-invent the matrix:
| Dress rehearsal field | Thursday R-gate |
|---|---|
channel_label_match |
R6 |
channels[] length ≥ 2 |
R5 / R6 |
dual_sku_dress_rehearsal_receipt path |
R10 evidence linkage |
build_id |
R2 / R3 diff anchor |
If Thursday diff shows channel_label_match flipped from true to false after a mid-week itch-only upload, treat rehearsal as stale—re-run D1–D6 before promotion even if Wednesday smoke stayed GREEN.
Schedule pairing for September 2026
| When | Pair with |
|---|---|
| After localization freeze | Freeze evening locales on both SKUs |
| Same week as promotion | Wednesday smoke then Thursday row review |
| Before marketing spend | itch PWYW economics |
| If funnel splits post-launch | Forward blog #5 wishlist recovery case study |
Key takeaways
- Dual-SKU fest prep needs a dress rehearsal, not hope that two uploads stayed aligned.
- D1 locks one
build_idfor the candidate rehearsal night. - D2 documents itch and Steam in
channels[]with URLs and upload UTC. - D3 requires
channel_label_matchacross visible labels—or a numbered exception. - D4 proves bytes with etag / SHA-256 /
steam_build_id, not upload progress bars. - D5 smokes itch browser and Steam client, not Editor-only paths.
- D6 files
dual_sku_dress_rehearsal_receipt_v1.jsonand setsdual_sku_rehearsal_ok. - Append two
upload_log.csvrows—one per portal, samebuild_id. - Thursday row review consumes
channel_label_match; this tutorial prevents R6 surprises. - Economics worksheet answers runway; this tutorial answers which build players get.
- Re-run rehearsal when either channel uploads—even if the other did not change.
- Forward blog #5 when rehearsal was green but wishlists still split after launch.
FAQ
What is a dual-depot dress rehearsal?
A one-evening evidence pass that proves itch and Steam fest channels reference the same build_id intent, agree on channel_label_match, and boot on both surfaces before October upload peak.
Is this the same as Thursday BUILD_RECEIPT row review?
No. Thursday diffs receipt rows weekly (R1–R10). This tutorial creates the initial dual-channel proof that makes R6 trustworthy.
Do itch and Steam binaries need identical SHA-256 hashes?
Not when surfaces differ (HTML5 vs Win64). D4 proves each channel serves its correct candidate for the declared build_id.
Can I skip itch if we only care about Steam fest?
Yes if itch is not a fest-facing channel. Remove itch from marketing and D2—or include it in the rehearsal.
How does this relate to localization freeze?
If both SKUs claim the same locales, run freeze evening first and link its receipt path in D6.
What if channel_label_match is false on purpose?
Document label_exception_id, owner, and expiry. Keep dual_sku_rehearsal_ok false until labels align or exception is approved for promotion.
Does this replace itch Butler QA tutorials?
No. Use itch ninety-minute QA pass for upload mechanics; this article owns cross-channel receipt proof.
When should we re-run the rehearsal?
After any upload to itch or Steam that changes player-visible bytes or labels—even mid-September.
Related reads
- Your First BUILD_RECEIPT JSON and Upload Log - One Evening
- Localization String Freeze Receipt Before October PO Import
- Thursday BUILD_RECEIPT Row Review - Ten Minutes After Wednesday Smoke
- Wednesday Demo Build Smoke Ritual
- itch PWYW vs Steam Demo SKU Economics Worksheet
- We Recovered a Dual-SKU Wishlist Split After itch Diluted Steam Fest Traffic — R1–R6 when rehearsal GREEN but discovery still leaks
- 14 Free IARC PEGI ESRB Age-Rating Packet Tools — soft-launch gate three (
age_rating_packet_ok) beside dual-SKU + freeze - itch.io HTML5 WASM MIME Cache After Upload
- BUILD_RECEIPT channel_label_match Triple HTML5 Fix
- Stop Shipping Fest Demos Without Player-Visible Build Label
- Stop Claiming Demo Progress Cloud-Syncs Without Numbered MIGRATE Exceptions — after D6 GREEN if FAQ promises demo→full carry-over
Closing
October upload peak is the wrong week to learn itch and Steam were never the same build. Spend one evening on the channel matrix, label match, byte proof, and dual smoke—then file dual_sku_rehearsal_ok only when D1–D6 are honestly green. If wishlists still stall after GREEN bytes, run the wishlist-split recovery case study (R1–R6) instead of declaring fest discovery healthy.
When marketing asks to push the itch link before Steam is ready, you will have a receipt that says yes or no with evidence—not vibes. That is the whole job of a dual-depot dress rehearsal before October Next Fest.