Stop Claiming Demo Progress Cloud-Syncs to Full Game Without a Numbered Migration Exception List - 2026 Opinion

September 2026: the Next Fest demo FAQ says progress carries over to the full release. Steamworks shows Steam Cloud enabled on the demo depot. Engineering knows the demo serializes player_v3.json while the full-game branch still reads save_slot_0.dat with a different schema version. October upload week: players buy the full SKU, cloud sync flickers, and hour-one progress is empty—or worse, corrupt.
September–October 2026 is when compassionate carry-over marketing collides with schema reality. PATCH exception opinion disciplines Addressables catalog bumps. MERGE exception disciplines trunk integration. Neither owns demo → full-game save migration when cloud namespaces, slot keys, or serialization formats diverge.
This Opinion & Hot Takes piece makes a narrow claim: if store copy, FAQ, or About bullets promise demo progress syncs or carries into the full game via Steam Cloud (or any cross-SKU path), every schema/key/namespace change that affects that promise must appear as the next integer in cloud_save_migration_exception_list—MIGRATE-001…N—with gates M1–M6, two-machine proof, and BUILD_RECEIPT cloud_migration_exception_list_ok: true. Otherwise rewrite copy to local-only or no carry-over** per cloud saves hour-one parity.
Non-repetition note: Cloud saves parity checklist owns hour-one Settings/FAQ truth for fest demo. PATCH owns remote catalog ships. MERGE owns trunk merges. Dual-depot dress rehearsal owns channel labels, not save schema. This URL owns numbered MIGRATE exceptions when demo progress is claimed to carry to full game.
Pair cloud saves parity, PATCH cousin, MERGE cousin, dual-SKU dress rehearsal, Help #59 forward, forward blog #11 Friday localization freeze capstone.
Why this matters now (September–October 2026)
- Carry-over is trendy copy — Soft-launch and fest pages borrow full-game infrastructure language.
- Same Steam app, different depots — Cloud config often shared while save paths differ per branch.
- Dual-SKU itch + Steam — Dress rehearsal proves channel labels; it does not prove save migration.
- Refund signal spikes — “Lost demo progress” tickets cluster when cloud sync lies (refund dashboard).
- October upload has no rewrite week — Store copy freeze meets schema drift without a numbered list.
Direct answer: Maintain cloud-save-migration-exception-list-2026-october.md with monotonic MIGRATE-001…N, file cloud_save_migration_exception_list_receipt_v1.json, run two-machine demo→full proof for each exception, set cloud_migration_exception_list_ok: true—or remove carry-over claims from FAQ/About until honest.
The opinion in one paragraph
Demo carry-over is not a vibes sentence. If marketing writes “your progress follows you into the full game” while engineering has not filed MIGRATE-001 with schema_version_before, schema_version_after, cloud_namespace, and a passing two-machine test, you did not promise continuity—you promised archaeology. Micro-studios cannot afford refund threads and partner diligence screenshots where FAQ claims cloud sync and Settings shows conflicting slot labels. Numbered MIGRATE lists are the minimum correlation layer between demo builds, full-game RC branches, Steam Cloud API rows, and store copy. Verbal “we’ll migrate saves at 1.0” is how teams enter October with save slot mismatch recovery folklore.
Who this opinion is for
| Audience | Why read |
|---|---|
| Solo founder pre-October | Copy-paste “next MIGRATE number or rewrite FAQ” |
| Producer on dual-SKU launch | Block carry-over copy until list + parity GREEN |
| Engineer / save-system lead | Wire verify_cloud_migration_exception_list.sh |
| Writer / community | Align FAQ with observed cloud behavior |
SHIP vs HOTFIX vs MERGE vs PATCH vs MIGRATE — format ladder
| List | Window | Schema | Primary gate |
|---|---|---|---|
| SHIP | Dec freeze → Jan re-entry | ship_exception_list_receipt_v1 |
O1–O6 |
| HOTFIX | Feb live D1–D7 | hotfix_exception_list_receipt_v1 |
H1–H6 |
| MERGE | Mar post-fest | merge_exception_list_receipt_v1 |
M1–M6 (merge) |
| PATCH | Apr–Jun content-update | patch_exception_list_receipt_v1 |
P1–P6 |
| MIGRATE | Sep–Oct demo→full | cloud_save_migration_exception_list_receipt_v1 |
M1–M6 (migration) |
Rule: PATCH rows do not substitute for MIGRATE when the ship changes save schema or cloud namespace—catalog hash ≠ player progress. MERGE rows do not substitute when the merge did not include a documented migration path and two-machine proof.
Beginner path — MIGRATE list in 25 minutes
Copy into release-evidence/saves/cloud-save-migration-exception-list-2026-october.md:
# Numbered MIGRATE exception list (October 2026 demo → full)
migration_window: 2026-10-next-fest
list_owner: producer
next_seq: 2
policy: demo_progress_claimed ⇒ every schema/key/namespace change is MIGRATE-00N
| Seq | Summary | demo_build | full_build | schema_before | schema_after | cloud_namespace | Two-machine pass? | Owner |
|-----|---------|------------|------------|---------------|--------------|-----------------|-------------------|-------|
| MIGRATE-001 | Slot key rename demo→full | demo-2026-09-12 | full-rc-2026-10-01 | v3 | v4 | AppID/Cloud/demo | yes | eng |
| MIGRATE-002 | (reserved) | — | — | — | — | — | — | — |
Beginner rules:
- If FAQ mentions carry-over, list cannot be empty—or rewrite FAQ to local-only.
- Next schema change touching carry-over =
next_seq—no MIGRATE-003 before MIGRATE-002 exists. - Two-machine test required when
demo_progress_claimed: trueon receipt. - Separate app IDs ⇒ separate MIGRATE row per migration path (Steam demo app ≠ full app).
- Capsule/marketing-only edits ⇒ not MIGRATE—this list is save identity, not screenshot polish.
Success check: Producer answers “what build pairs does carry-over cover?” by reading highest Seq and demo_build/full_build columns.
Developer path — gates M1–M6 (migration schema)
These M gates belong to cloud_save_migration_exception_list_receipt_v1—not MERGE M1–M6 (different receipt, same letter rhythm).
| Gate | Check | Fail action |
|---|---|---|
| M1 | exceptions[] seq integers 1..N contiguous |
Fix gaps before full-game promote |
| M2 | next_seq == N+1 in markdown matches JSON |
Sync list + receipt |
| M3 | Each row with carry-over claim has two_machine_test_path |
Block store copy |
| M4 | schema_version_after on last exception matches full-game BUILD_RECEIPT |
Fix lineage |
| M5 | cloud_namespace / Steam Cloud API rows documented per exception |
Reject silent namespace swap |
| M6 | cloud_migration_exception_list_ok: true + producer sign-off |
Fail upload window / capstone |
cloud_save_migration_exception_list_receipt_v1.json (template)
{
"schema": "cloud_save_migration_exception_list_receipt_v1",
"migration_window": "2026-10-next-fest",
"generated_at": "2026-09-20T18:00:00Z",
"next_seq": 2,
"demo_progress_claimed": true,
"store_copy_pointer": "release-evidence/store/save-claim-proof.md",
"exceptions": [
{
"seq": 1,
"id": "MIGRATE-001",
"summary": "Rename player_v3.json → save_slot_0 unified schema v4",
"demo_build_label": "demo-2026-09-12",
"full_build_label": "full-rc-2026-10-01",
"schema_version_before": 3,
"schema_version_after": 4,
"cloud_namespace": "AppID/Cloud/demo",
"cloud_namespace_after": "AppID/Cloud/full",
"migration_script_path": "tools/saves/migrate_v3_to_v4.py",
"two_machine_test_path": "release-evidence/saves/two-machine-migrate-001.md",
"touches_steam_cloud": true,
"producer_signoff_path": "release-evidence/saves/MIGRATE_001_SIGNOFF.json",
"owner": "eng"
}
],
"pointers": {
"cloud_saves_parity": "release-evidence/saves/save-claim-proof.md",
"dual_sku_rehearsal": "release-evidence/dual-sku/dual_sku_dress_rehearsal_receipt_v1.json",
"localization_freeze": "release-evidence/localization/localization_freeze_receipt_v1.json"
},
"gates": {
"M1": "pass",
"M2": "pass",
"M3": "pass",
"M4": "pass",
"M5": "pass",
"M6": "pass"
},
"cloud_migration_exception_list_ok": true
}
BUILD_RECEIPT latch:
{
"save_policy": "demo_carry_over_documented",
"cloud_migration_exception_list_ok": true,
"cloud_migration_exception_count": 1,
"cloud_migration_exception_list_receipt": "release-evidence/saves/cloud_save_migration_exception_list_receipt_v1.json"
}
Two-machine demo → full proof (required for M3)
| Step | Machine A (demo) | Machine B (full RC) |
|---|---|---|
| 1 | Install fest demo branch | — |
| 2 | Play 20 min, trigger save, note slot | — |
| 3 | Export cloud file hash if cloud ON | — |
| 4 | — | Install full-game RC on same Steam account |
| 5 | Run migration path (first launch or explicit import) | Observe slot |
| 6 | — | Progress matches documented carry-over scope? |
| 7 | Screenshot Settings + slot UI both sides | Archive under two-machine-migrate-NNN.md |
Pass rule: Observed behavior matches exact FAQ scope (chapter 2 only, meta currency capped, etc.)—not marketing extrapolation.
Outbound: Steamworks Steam Cloud documentation for API and namespace settings.
verify_cloud_migration_exception_list.sh
#!/usr/bin/env bash
set -euo pipefail
R="${1:-release-evidence/saves/cloud_save_migration_exception_list_receipt_v1.json}"
jq -e '.schema == "cloud_save_migration_exception_list_receipt_v1"' "$R" >/dev/null
jq -e '.cloud_migration_exception_list_ok == true' "$R" >/dev/null
for g in M1 M2 M3 M4 M5 M6; do
test "$(jq -r --arg g "$g" '.gates[$g]' "$R")" = "pass"
done
CLAIMED=$(jq -r .demo_progress_claimed "$R")
COUNT=$(jq '.exceptions | length' "$R")
if [ "$CLAIMED" = "true" ] && [ "$COUNT" -lt 1 ]; then
echo "FAIL: demo_progress_claimed but exceptions empty"
exit 1
fi
jq -e '.exceptions | (map(.seq) | sort) == (range(1; length+1) | .)' "$R" >/dev/null
if [ "$CLAIMED" = "true" ]; then
jq -e '.exceptions[] | select(.two_machine_test_path | length > 0)' "$R" >/dev/null
fi
echo "cloud_migration_exception_list_ok"
Store copy coupling (honesty table)
| Copy location | If carry-over claimed | If local-only demo |
|---|---|---|
| FAQ | MIGRATE list + parity GREEN | Cloud saves checklist |
| About bullets | Same as FAQ | Say “demo progress does not carry” |
| Steam Cloud toggle | Document namespace per MIGRATE | Cloud OFF or isolated demo namespace |
| Event posts | No “buy full, keep save” without M6 | OK with honest scope |
| Discord pin | Link exception list or rewrite | Link local-only policy |
Triangle-diff FAQ, About, and Settings before October upload—same bar as event posts parity.
Scenarios A–H
A — FAQ promises carry-over; list empty
Fail M6. Rewrite FAQ or file MIGRATE-001 before upload.
B — Schema bump without new MIGRATE row
Fail M4. Increment list; re-run two-machine proof.
C — Cloud enabled on demo, local-only truth
Not a MIGRATE problem—fail cloud parity; disable cloud or fix copy.
D — itch HTML5 + Steam demo different save paths
Separate rows if claiming cross-channel carry-over; dual-depot proves labels, not migration.
E — MERGE-003 touched save code without MIGRATE
MERGE ≠ MIGRATE. Add MIGRATE row if carry-over scope changed.
F — PATCH-002 catalog bump only
PATCH list only—no MIGRATE unless save serialization in bundles changed player state.
G — Player cloud overwrite after enabling cloud mid-fest
Recovery path: Steam Cloud overwrite help—then MIGRATE if carry-over still claimed.
H — Full game launches months after demo; schema v5
New MIGRATE-002 for v4→v5 if copy still promises demo carry-over; old MIGRATE-001 remains historical record.
Common mistakes
- Using “Steam Cloud ON” as proof of migration.
- Claiming carry-over for currency demo grants without cap in exception row.
- Mixing MERGE/PATCH ids into MIGRATE markdown.
- Skipping two-machine test on producer laptop only.
- Separate full-game app ID with no cross-app migration code—still claiming sync.
- Letting community moderators promise carry-over in Discord without list.
- Filing MIGRATE after upload instead of before store copy freeze.
Troubleshooting
“Migration script only runs on dev machines.” Ship script path in receipt; CI smoke import on clean account.
“Demo and full use different Steam apps.” Document cross-app migration explicitly or remove carry-over copy.
“Players expect full roguelike meta carry.” Narrow FAQ scope or add MIGRATE row with capped import rules.
“Cloud namespace unknown.” M5 fail until Steamworks screenshot in evidence folder.
Producer waiver (when M gate overridden)
# MIGRATE spend/copy waiver
Date:
Gate overridden: M{1-6}
Why:
Store copy change required (Y/N):
New FAQ text attached (Y/N):
Producer sign:
Follow-up before upload:
Waivers do not replace empty lists when carry-over remains in FAQ.
Engine-specific migration notes (working dev)
These are receipt fields, not engine endorsements—document paths in MIGRATE rows.
| Engine | Typical demo→full risk | Receipt field to pin |
|---|---|---|
| Unity | PersistentDataPath + cloud sync plugin version |
save_root, serializer_version |
| Godot | user:// path + .save resource format |
godot_save_resource, cfg_version |
| Ren'Py | renpy.save slot + rollback depth |
slot_name, rollback_policy |
| GameMaker | save_dir + ini vs json swap |
save_filename, format_id |
| Unreal | .sav + platform cloud wrapper |
ue_save_game_class, cloud_file_name |
If demo and full game ship different engine minors, assume MIGRATE required until two-machine proof says otherwise.
Migration script minimum bar
Any migration_script_path in MIGRATE-00N must:
- Be callable from a clean install (first-launch hook or menu “Import demo save”).
- Log
schema_version_before→afterto a file underrelease-evidence/saves/. - Fail closed on unknown versions (no silent wipe).
- Include idempotent guard (
if already v4, skip). - Ship in full-game RC build referenced by
full_build_label.
# smoke hook example (CI or facilitator laptop)
py tools/saves/migrate_v3_to_v4.py --input demo_export.sav --output full_slot_0.sav --dry-run
test $? -eq 0
RED / YELLOW / GREEN routing (M gates)
| Color | Condition | Producer action |
|---|---|---|
| RED | Carry-over in FAQ; exceptions[] empty or M3 missing |
Block upload; rewrite copy or file MIGRATE |
| YELLOW | List filed; two-machine pass pending | Soft-launch may continue; no “buy full keep save” ads |
| GREEN | M1–M6 pass; latch true | October capstone may read migration row |
Forward #11 Friday capstone should treat RED migration as blocking as stale localization_freeze_ok.
Diligence zip crosswalk (three save artifacts)
Partners often ask for one folder—give three honest files:
| File | Answers |
|---|---|
save-claim-proof.md |
What demo claims today (parity cousin) |
cloud_save_migration_exception_list_receipt_v1.json |
What demo→full changes were authorized |
two-machine-migrate-NNN.md |
What players actually saw |
Missing any leg when carry-over is marketed is diligence theater.
Soft-launch ad copy stop rule
If soft-launch windows G gates GREEN but M latch RED, creatives may not use “keep your progress” hooks—same hierarchy as wishlist recovery case studies. Funnel honesty beats CTR.
What “carry-over” must mean in writing (copy lawyer for engineers)
When MIGRATE is required, FAQ language should be narrow:
| Vague (reject) | Narrow (acceptable with MIGRATE proof) |
|---|---|
| Progress syncs everywhere | Demo chapter 1–3 progress imports once at full-game first launch |
| Cloud saves follow you | Steam Cloud demo namespace X migrates to full namespace Y per MIGRATE-001 |
| Keep your save | Meta currency capped at 500; inventory slots 1–4 only |
| Full game remembers demo | Requires same Steam account; one-time import; no cross-platform |
Writers paste the narrow sentence into FAQ only after engineering attaches the matching MIGRATE row. Marketing shorthand without row attachment is how refund dashboards spike.
October upload week producer card
## Demo→full migration card (10 min)
- [ ] FAQ still claims carry-over? (Y/N)
- [ ] If Y: cloud_migration_exception_list_ok GREEN?
- [ ] Highest MIGRATE id: ___
- [ ] two_machine test date: ___
- [ ] Steam Cloud screenshot in evidence folder?
- [ ] If N: FAQ explicitly denies carry-over?
- [ ] dual_sku_rehearsal_ok still GREEN?
Sign: ______
Pin beside localization freeze capstone forward #11 ritual—different columns, same Friday discipline.
Evidence folder layout
release-evidence/saves/
cloud-save-migration-exception-list-2026-october.md
cloud_save_migration_exception_list_receipt_v1.json
save-claim-proof.md
two-machine-migrate-001.md
MIGRATE_001_SIGNOFF.json
steamworks-cloud-screenshots/
Pair October cluster receipts
| Receipt | Role vs MIGRATE |
|---|---|
| Localization freeze | Strings locked—not save schema |
| Weekly locale jq | Locale IDs—not cloud keys |
| Soft-launch windows | Traffic timing—not migration |
| Forward #11 capstone | Row-review includes cloud_migration_exception_list_ok |
FAQ
Can we ship demo and full on different months?
Yes—MIGRATE row must reference both demo_build_label and full_build_label even if months apart; store copy must not promise carry-over during the gap unless import path exists on full launch day.
Who signs M6?
Producer plus save-system owner—same dual sign pattern as PATCH P6 and MERGE M6 cousins.
Is this the same as cloud saves hour-one parity?
No. Parity proves demo Settings match FAQ today. MIGRATE proves demo→full path when copy claims carry-over.
Same as PATCH exception list?
No. PATCH is Addressables/catalog identity. MIGRATE is player save schema and cloud namespace.
Same as MERGE M1–M6?
Same letter prefix, different receipt file and gates. Do not merge JSON.
Can we claim carry-over with local-only demo?
Yes if FAQ says no cloud carry-over and parity GREEN—MIGRATE list may be empty with demo_progress_claimed: false.
What about console ports later?
New migration window, new list series—do not append console rows to October Steam list without schema note.
Does dual-SKU dress rehearsal replace MIGRATE?
No. D1–D6 prove channel labels and depots; MIGRATE proves save continuity claims.
Help #59 forward?
Fix article for “cloud sync overwrote demo save”—recovery, not authorization to claim carry-over.
When is the opinion satisfied?
M1–M6 pass, copy matches proof, latch set—or carry-over language removed from store.
Key takeaways
- Carry-over copy requires MIGRATE-001…N—not PATCH/MERGE cousins.
- Gates M1–M6 on
cloud_save_migration_exception_list_receipt_v1.json. - Latch
cloud_migration_exception_list_okon BUILD_RECEIPT. - Two-machine demo→full proof mandatory when carry-over claimed.
- Cloud saves parity is prerequisite honesty.
- Document
schema_version,cloud_namespace, build labels per row. - Empty exceptions allowed only when
demo_progress_claimed: false. - PATCH catalog ships ≠ save migration.
- Dual-depot dress rehearsal ≠ save schema.
- Forward #11 capstone should read migration latch.
- Help #59 forward for overwrite recovery.
- Rewrite FAQ beats silent schema drift before October upload.
Related reads
- Steam Cloud Saves and Progression Claims vs Demo Hour-One Parity
- Stop Shipping Content Updates Without a Numbered PATCH Exception List
- Stop Merging Post-Fest Branches Without a Numbered MERGE Exception List
- Your First itch.io + Steam Dual-Depot Dress Rehearsal
- We Recovered Mismatched Save Slot Labels After Steam Branch Promotion
- Steam Cloud Sync Overwrites Local Fest Demo Save
- Your First Steam Refund Signal Dashboard
- August 2026 Soft-Launch Windows Before October Next Fest
Closing
Progress carries over is a schema claim, not a marketing adjective. Number demo→full migrations, prove them on two machines, file cloud_save_migration_exception_list_receipt_v1.json, or delete the sentence from FAQ before October traffic arrives.
PATCH lists protect catalog truth. MERGE lists protect trunk truth. MIGRATE lists protect player trust when demo nights feed full-game wishlists.