We Recovered Mismatched Save Slot Labels After Steam Branch Promotion - 2026 Case Study
This is a synthesized case study—a field pattern seen across May–October 2026 fest demos on Steam Windows builds, especially teams that promoted a playtest or internal branch to a public fest depot without updating save slot label maps. It is not a named studio turnaround story. There are no invented revenue figures, no fake Discord quotes, and no made-up refund percentages.
What follows is the failure signature, the isolation order that worked, the artifacts that proved recovery, and how the same pattern maps to your project if players say “I saved in Slot 1 and it loaded empty.”
Pair this narrative with GDevelop save-path freeze, GameMaker export sanity, Ren'Py label freeze, and BUILD_RECEIPT so you have procedure plus story.
Non-repetition note: Freeze-week posts teach prevention. This case study describes what broke after promotion when slot UI and persistence keys diverged—not another seven-day gate list.
Why this matters now (May–October 2026)
- Weekly fest promotions — Producers merge
playtestintofest_publicwithout a slot-label diff. - “Lost progress” threads — Comments rhyme with
gameplay/savetags in refund dashboards—but root cause is label map drift, not cloud outages. - Multi-slot UI debt — Menu shows Slot 1 while code writes
save_slot_bafter a refactor. - Cross-engine blind spots — GDevelop
Storagekeys, GameMakerinisections, and Ren'Pypersistentfields each fail differently; the symptom is identical. - Evidence gap — Teams ran Wednesday smoke on boot path only, not save/load matrix.
Direct answer: Recovery required save_slot_label_map_v1.json, a promotion-day slot round-trip on installed Steam build, save_slot_recovery_receipt_v1.json, and a BUILD_RECEIPT row tying build_label to the map hash before the next branch merge.
Beginner quick start — what recovered means here
Recovered in this pattern means:
- Reproduced wrong slot load on installed Steam build—not editor only.
- Mapped every UI label to exactly one persistence key per
build_label. - Logged receipt JSON and one screen recording of save → quit → load.
- Reduced new “lost save” comments for seven days on the same
build_label(qualitative, not a fabricated %).
It does not mean cloud saves work globally or that Steam Cloud was enabled—many fest demos are local-only with honest FAQ copy.
Success check: A producer can load Slot 2, quit, relaunch, and Slot 2 still shows the same chapter or floor—without engineer jargon.
The player-facing failure (typical signals)
| Channel | Typical wording |
|---|---|
| Steam discussion | “Slot 1 empty but I saved” |
| playtest form | “Wrong chapter after update” |
| refund language | “Lost all progress after patch” |
| stream clip | Clicks Slot 1; loads different timestamp |
Internal team language before fix: “Saves work on my machine” — because QA used one slot and local dist folder, not installed client with three labeled slots.
Starting state (what was wrong)
| Layer | Symptom |
|---|---|
| UI | Menu: Slot 1, Slot 2, Slot 3 |
| Code | Writes file_a, file_b, quicksave |
| Promotion | build_label bumped; map not updated |
| FAQ | “Three save slots” — true in UI, false in keys |
| Process | No slot matrix in promotion checklist |
| Playtest CSV | Missing slot_ui and persistence_key columns |
Gameplay was fine. Trust in saves was not.
Root cause pattern (synthesized)
Three recurring mechanics—often combined:
| Mechanic | What drifted |
|---|---|
| Index off-by-one | UI Slot 1 → slot_index=0 in old build, slot_index=1 after merge |
| Renamed keys | Refactor save1 → demo_save_v2 without UI migration |
| Branch scope mix | Playtest branch wrote full_game_slot_1; fest demo UI still shows three demo slots |
None require malice—only missing map file at promotion time.
Timeline (five working days — pattern timing)
| Day | Focus | Output |
|---|---|---|
| D1 | Repro on installed Steam | Recording + fail log |
| D2 | Inventory UI strings + keys | save_slot_label_map_v1.json draft |
| D3 | Fix mapping + migration message | Code or script patch |
| D4 | Three-slot round-trip matrix | gate_slot_matrix.log |
| D5 | Receipt + BUILD_RECEIPT row | save_slot_recovery_receipt_v1.json |
Teams with existing GDevelop freeze save_path_map.json recovered in two days; teams without any map took five.
Hour-by-hour D1 (reproduction discipline)
| Step | Action |
|---|---|
| 1 | Install from Steam client (or playtest branch players use) |
| 2 | Note build_label on title screen |
| 3 | Save mid-demo in Slot 1; note chapter/floor shown |
| 4 | Quit fully |
| 5 | Relaunch; load Slot 1 — record pass/fail |
| 6 | Repeat for Slot 2 and Slot 3 |
If Slot 1 loads empty but Slot 2 shows Slot 1’s progress—you are in this case study.
D2 — save_slot_label_map_v1.json (inventory)
Create release-evidence/saves/save_slot_label_map_v1.json:
{
"schema": "save_slot_label_map_v1",
"build_label": "fest-demo-2026-05-24-rc2",
"slots": [
{
"ui_label": "Slot 1",
"ui_index": 1,
"persistence_key": "demo_save_slot_a",
"storage_surface": "local_file",
"expected_path_hint": "%APPDATA%/Studio/Game/saves/"
},
{
"ui_label": "Slot 2",
"ui_index": 2,
"persistence_key": "demo_save_slot_b",
"storage_surface": "local_file",
"expected_path_hint": "%APPDATA%/Studio/Game/saves/"
},
{
"ui_label": "Quicksave",
"ui_index": 0,
"persistence_key": "quicksave",
"storage_surface": "local_file",
"notes": "Hidden from fest UI—must not steal Slot 1 key"
}
],
"faq_claim": "three manual slots local only",
"cloud_enabled": false
}
Pass: Every visible UI slot has exactly one persistence_key; no duplicate keys.
Engine-specific inventory notes
| Engine | Where keys hide |
|---|---|
| GDevelop | Storage action keys in save_path_map |
| GameMaker | ini section names + slot functions |
| Ren'Py | FileSave / FileLoad slots + persistent |
| Construct | Local Storage keys per save semver |
| Godot | user://save_%d.save pattern |
One map file per SKU—do not merge demo and full-game maps without a sku_id column.
D2 deep dive — grep and screenshot discipline
Programmers export evidence—not opinions:
| Artifact | Contents |
|---|---|
| UI screenshot | All slot strings visible on load menu |
grep log |
Every save, Storage, FileSave, ini key |
| BUILD_RECEIPT prior row | Previous build_label for diff |
| Disk listing | Filenames under save root (redact usernames) |
Pass: Screenshot string Slot 1 appears on same line in map JSON as ui_label, and persistence_key appears in grep log exactly once.
Studios using validate-packet added a optional check: save_slot_label_map_v1.json must exist when demo_has_save_slots=true in packet manifest.
D3 — fix mapping and migration
Minimum fix set:
- Align
ui_indextopersistence_keyin code or rename keys to match shipped UI. - On load, if old key exists under legacy name, migrate once with logged message.
- Show
save_incompatiblelabel instead of silent empty slot when migration impossible.
Example migration block (conceptual—adapt to engine):
IF legacy_key "save1" exists AND "demo_save_slot_a" empty
COPY legacy → demo_save_slot_a
DELETE legacy
LOG "migrated save1 → demo_save_slot_a for build_label rc2"
Scope rule: No new features during D3—mapping fixes only.
Align FAQ save lines with faq_claim row in map.
D4 — slot round-trip matrix
Log gate_slot_matrix.log:
| UI slot | Save OK | Load OK | Timestamp matches | Pass |
|---|---|---|---|---|
| Slot 1 | Y | Y | Y | Y |
| Slot 2 | Y | Y | Y | Y |
| Slot 3 | Y | N | N | N |
Run matrix on:
- Fresh install (no prior saves)
- Install after one promotion (simulates fest player updating mid-week)
Pair with metadata diff on same promotion day—store copy and slot behavior ship together.
D5 — save_slot_recovery_receipt_v1.json
{
"schema": "save_slot_recovery_receipt_v1",
"build_label": "fest-demo-2026-05-24-rc2",
"map_path": "release-evidence/saves/save_slot_label_map_v1.json",
"map_sha256": "sha256:example…",
"matrix_log": "release-evidence/saves/gate_slot_matrix.log",
"slots_tested": 3,
"matrix_pass": true,
"migration_applied": true,
"recovery_pass": true,
"promotion_allowed": true,
"signed_at": "2026-05-28T17:00:00Z",
"reviewer": "GamineAI Team",
"notes": "Installed Steam build; cloud off; pairs GDevelop freeze week 2026-W21"
}
Attach to BUILD_RECEIPT upload row:
| Field | Value |
|---|---|
save_slot_recovery_pass |
true |
save_slot_map_sha256 |
(from receipt) |
What promotion day should have caught
| Check | Owner | Time |
|---|---|---|
| Slot matrix on installed build | QA | 15 min |
Map file diff vs previous build_label |
Programmer | 5 min |
FAQ vs faq_claim |
Producer | 5 min |
| BUILD_RECEIPT hash | Producer | 2 min |
Total ~27 minutes—cheaper than five-day recovery.
Add row to Wednesday smoke optional S9: “Slot 1 round-trip pass.”
Refund tag crosswalk (qualitative)
In refund dashboard spreadsheets, teams reported clusters of:
| Tag | Often actually |
|---|---|
save / progress |
Slot label mismatch |
cloud |
Local-only demo; player expected sync |
update broke game |
Migration missing after key rename |
After receipt shipped, new rows on same build_label with save language dropped in several teams’ qualitative notes—not a guaranteed metric for your studio.
Playtest form fields (D2 add-on)
| Field | Why |
|---|---|
build_label |
Correlate map version |
ui_slot |
What player clicked |
last_chapter_or_floor |
Detect wrong load |
installed_from_steam |
Y/N |
Use 18 playtest tools for intake—require these four fields on save-related tickets.
Branch promotion failure modes
| Failure | Symptom |
|---|---|
| Merged playtest saves into fest depot | Slot 3 shows dev data |
| Promoted without wiping QA saves | Players inherit tester slots |
build_label changed; map did not |
Empty Slot 1 |
| Hotfixed UI strings only | Keys still wrong |
Rule: Promotion checklist includes map hash diff, not only binary smoke.
Comparison to replay refresh case study
| Dimension | Browser refresh replay case | This slot-label case |
|---|---|---|
| Surface | itch F5 / RNG | Steam slot UI |
| User action | Refresh tab | Load save menu |
| Fix type | Sheet order + seed | Label map + migration |
| Receipt | rng_replay_receipt |
save_slot_recovery_receipt |
| Timeline | ~5 days | ~5 days typical |
Both live under release-evidence/—different subfolders.
Comparison to Ren'Py persistent drift
Ren'Py freeze week prevents label renames mid-fest. This case study covers UI slot numbers vs persistence keys after branch promotion—VN teams hit both; run freeze week before promotion, slot map on promotion day.
Developer checklist (PR template)
- [ ]
save_slot_label_map_v1.jsonmatches UI screenshot - [ ] No duplicate
persistence_keyvalues - [ ] Slot matrix pass on installed Steam build
- [ ]
save_slot_recovery_receipt_v1.jsonupdated - [ ] FAQ save claims match
faq_claim - [ ] BUILD_RECEIPT row includes map hash
- [ ] Migration log attached if keys renamed
Producer one-pager
Save slot honesty means Slot 1 on screen writes to the key players expect after every promotion. We prove it with
save_slot_recovery_receipt_v1.jsonbefore fest branch merge. Playtesters: send build_label and which slot you used.
Anti-patterns in the same threads
| Anti-pattern | Why it fails |
|---|---|
| “Works in editor” | Wrong install surface |
| Testing only Slot 1 | Slot 2 drift hidden |
| UI rename without key map | Silent empty load |
| Blaming Steam Cloud | Cloud was off in FAQ |
| Closing tickets without receipt | Regression on next merge |
Ninety-minute “are we in this case study?” audit
| Minute | Question |
|---|---|
| 0–15 | Does Slot 1 round-trip on installed Steam? |
| 15–30 | Does map file exist in repo? |
| 30–45 | Do UI labels match map JSON? |
| 45–60 | Does FAQ match faq_claim? |
| 60–90 | Write pass/fail in qa-slot-label.log |
If first step fails—you are in this case study.
Incident retrospective (45-minute workshop)
- Why was slot matrix missing from promotion checklist?
- Who owns map updates when UI strings change?
- Do playtest forms capture
ui_slot? - Is playtest branch isolated from public fest depot?
- Does Wednesday smoke include save round-trip?
Forward-link: playtest invite isolation playbook when backlogged—separate scopes need separate maps.
When this case study does not apply
- Single-slot arcade games with no load menu
- Roguelites with one run autosave only (see replay case study instead)
- Games with no Steam branch promotions this quarter
- Teams that only ship itch HTML5 without labeled slots
Community response template (Steam discussion)
Thanks for the report. Build fest-demo-2026-05-24-rc2 fixed a slot-label mismatch from the previous branch—please update. If you are on rc2 and Slot 1 still loads empty, reply with build_label from the title screen and which slot you used so we can match your save file.
No invented stats—build label and slot only.
Cost of five-day delay (opportunity framing)
| Delay day | Risk |
|---|---|
| D1 skipped | More “lost save” posts |
| D2 skipped | Map lies |
| D3 skipped | Wrong migration |
| D4 skipped | Slot 3 still broken |
| D5 skipped | Promoted lying binary |
Five days beats two weeks of fest-week comment firefighting.
Cross-training for non-programmers
Producers can run D1 matrix without opening engine:
- Install Steam build from checklist.
- Play to mid-demo.
- Save each slot; quit; reload each.
- Screenshot pass/fail table.
- File form.
Engineering fixes maps; whole team detects.
Link graph (save discipline family)
| Order | Post | Role |
|---|---|---|
| 1 | GDevelop freeze | Path prevention |
| 2 | Ren'Py freeze | VN prevention |
| 3 | GameMaker export | Export sanity |
| 4 | Construct save semver | Mid-fest patches |
| 5 | This case study | Post-promotion recovery |
New hires read 5 → 1 for motivation before procedure.
Cloud saves caveat
If cloud saves parity applies, extend map with cloud_enabled: true and Steam Cloud API row per slot. This synthesized pattern often involved local-only fest demos where players assumed cloud sync—fix FAQ before enabling cloud.
Defold and collection saves
Defold collection audit addresses save root discovery. Slot-label mismatch can still occur if UI uses slot numbers but collections write to a single game.state file—add collection_slot column to map JSON.
Audio and trust (tangential)
Players who mute after save frustration still leave bad reviews—optional menu loop check via LUFS listicle does not fix slots but reduces compound negative first impressions after a save scare.
Friday Block 5 line
Add to Friday Block 5:
slot_matrix=pass/fail; save_slot_receipt_path=release-evidence/saves/save_slot_recovery_receipt_v1.json
Fifteen seconds prevents promoting a build that only passed boot smoke.
What we would do differently (retrospective)
Ship save_slot_label_map_v1.json before first public fest promotion—not after the first “lost progress” thread. Run slot matrix on installed Steam the same hour as BUILD_RECEIPT upload. Treat map hash diff as a merge blocker equal to broken exe.
That ordering collapses this five-day pattern into one promotion afternoon—and leaves the team arguing about story and art during fest month instead of defending slot numbering in discussion threads.
Key takeaways
- Synthesized pattern—no invented studio metrics; portable artifact list.
- UI slot labels must map 1:1 to persistence keys per
build_label. save_slot_label_map_v1.jsonis the inventory heart.save_slot_recovery_receipt_v1.jsonproves recovery before next promotion.- Reproduce on installed Steam, not editor or dist folder only.
- Five-day timeline is indicative; prevention takes one promotion checklist.
- Pairs with GDevelop, Ren'Py, GameMaker, and Construct save discipline posts.
- Refund dashboard tags often mislabel slot bugs as cloud issues.
- Add S9 slot round-trip to Wednesday smoke optional gates.
- Migration messages beat silent empty slots.
- Playtest forms need
ui_slotandbuild_label. - Branch promotion without map diff is the usual trigger.
- Producers can run the ninety-minute audit without engine access.
- FAQ
faq_claimrow must match real slot count and cloud state.
FAQ
Is this the same as corrupt save files?
No—files may exist under a different key than the UI slot the player chose.
Do we need Steam Cloud?
Not for local-only demos—honest FAQ matters more than enabling cloud.
Does this replace GDevelop freeze week?
No—freeze prevents path bugs; this case study covers post-promotion label drift.
Can one slot work and two fail?
Yes—matrix testing all visible slots is mandatory.
Should quicksave share Slot 1’s key?
No—give quicksave its own persistence_key in the map unless FAQ says they are the same slot.
Post-recovery monitoring (seven-day qualitative window)
| Signal | Healthy after fix |
|---|---|
| New save-language comments | Flat or down vs prior build_label |
| Playtest forms citing wrong slot | Stop after map ships |
Refund rows tagged save |
Fewer new rows on fixed label |
| Internal chat | Fewer “cannot repro” threads |
No target percentages—track direction only.
Partner diligence attachment
Q3 diligence reviewers ask what happens to player progress when branches merge. Attach save_slot_label_map_v1.json, save_slot_recovery_receipt_v1.json, and optional 60s three-slot matrix capture—answers slot honesty without inventing retention stats.
Store-demo mismatch crosslink
When saves fail, players also report store-demo mismatch symptoms—treat slot map as binary truth beside metadata diff, not a separate crisis narrative.
Final honesty block
GamineAI Team publishes synthesized patterns to reduce repeated support questions across indie teams. Your comment volume, refund rate, and engine will differ. The artifact list is the portable part—timelines are indicative, not guarantees.
Reader assignment (optional)
Assign one person to run the ninety-minute audit and attach qa-slot-label.log to your next BUILD_RECEIPT. You either confirm you are not in this case study—or you start D1 the same day. Share the log in your next producer standup so art and narrative leads know saves are unblocked before trailer week.
Conclusion
Fest promotion without a slot-label receipt recreates “lost progress” threads even when saves exist on disk.
Map the slots. Test the matrix. File the receipt. Then merge the branch. Players forgive hard games faster than saves that lie about which file they touched.
Next reads: GDevelop save-path freeze, Wednesday demo smoke, BUILD_RECEIPT pipeline.