Construct 3 NW.js Steam Demo Saves to Wrong AppData Path After Fest Branch Promotion - How to Fix
Problem: Your Construct 3 NW.js build passed browser replay tests and Gate 7 on the event-sheet freeze challenge. After fest_demo Steam promotion, playtesters report progress resets every launch—or saves from the itch tab never appear on desktop. File Explorer shows localStorage under a different NW.js profile folder than the path in save_path_map.json.
Who is affected now: Teams shipping HTML5 → NW.js → Steam in May–October 2026 who renamed the product title, edited package.json name, or promoted a fest branch copied from internal QA without re-running save-path gates. The bug appears after branch promotion or manifest rename: browser preview, Thursday export folder, and Steam-installed exe use different storage origins.
Fastest safe fix: Freeze package.json name and Construct project name before freeze week ends → document %LOCALAPPDATA% NW.js profile path in save_path_map.json → diff maps and construct_nwjs_save_receipt_v1.json between internal and fest_demo → run one-time migration on first launch if early fest testers already saved under the old profile → set gate4_branch_save_path_match: pass before SteamPipe → add save_path to Wednesday metadata diff.
Direct answer
Progress is not “lost”—it is stored under an NW.js profile directory keyed by package.json name and export origin. Branch promotion copied events and NW.js output but not the locked manifest identity your freeze week documented. Construct Storage actions use the same keys while the underlying profile path moved. Fix the manifest, the map, the re-export, and the receipt together; re-testing in the browser preview does not validate fest behavior.
Why this issue spikes in June 2026
- The Construct NW.js save-path resource list and sheet-order freeze made
save_path_map.jsonstandard—teams promote branches beforegate4_branch_save_path_matchexists on fest evidence. - Steam fest_demo builds often inherit playtest exports where
package.jsonnamestill saysinternal_playtestwhile the store page shows a new product title. - Browser-first roguelites keep localStorage semantics; NW.js maps them to per-profile folders under AppData—preview origin ≠ packaged exe origin.
- Playtest vs fest_public isolation fails when both branches share one Storage key but different manifest names—see playtest isolation playbook.
Pair with 12 Free Defold GDevelop Ren'Py save-path audit templates. For the GDevelop parallel failure class, see GDevelop preview-folder save help.
Symptoms and search phrases
- Saves work in Construct browser preview; Steam-installed NW.js demo always starts at level one.
%LOCALAPPDATA%path contains an old game name or duplicate NW.js profile folders.- QA validated Thursday NW.js zip; fest_demo depot behaves differently.
- Storage key
SaveSlotunchanged but profile folder renamed after title change. construct_nwjs_save_receipt_v1.jsonon fest branch missing or stale vs internal.- Playtest branch saves persist; fest_public does not (shared key, different
package.jsonname). - After game update, players report “save deleted” with no error dialog.
Root causes (check in order)
package.jsonnamechanged between internal export and fest export—NW.js creates a new profile; oldlocalStorageinvisible.- Construct project display name changed without re-exporting NW.js—events reference old keys; disk path moved.
- Branch promotion copied NW.js folder only —
save_path_map.jsonupdated oninternal, not fest evidence folder. - Preview vs export tested — Gate 5 passed on browser tab; Gate 6 installed Steam never run on
fest_demo. - Hardcoded Storage key per branch (
demo_savevsdemo_save_preview) — UI shows slot 1 while disk uses slot 2. - Wrong depot promoted — playtest binary on fest branch (see save-slot label case study).
- Event sheet uses WebStorage actions on desktop — preview OK; NW.js profile path diverges at runtime.
Beginner path (first 20 minutes)
Prerequisites: Windows packaged NW.js build (not browser tab), Construct 3 project with Storage extension enabled, access to %LOCALAPPDATA%.
- Install the build from
fest_demo(or local NW.js exe with identical export settings). - Play to first save trigger; note the time.
- Open File Explorer →
%LOCALAPPDATA%→ search for folders modified in the last 10 minutes (often under a name matchingpackage.jsonname). - If the folder name does not match your
save_path_map.jsonnwjs_profile_name, you have confirmed the mismatch—continue to Fastest safe fix path Step 2.
Common mistake: Testing only inside Construct’s Run layout preview—always validate the exported NW.js exe Steam will ship.
Fastest safe fix path
Step 1 — Prove where the Steam build writes (packaged NW.js only)
- Install from
fest_demobranch (or fest-profile local export). - Delete documented profile folders listed in old
save_path_map.json(backup first). - Launch; trigger save at first checkpoint.
- Locate new files:
Get-ChildItem -Path $env:LOCALAPPDATA -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-10) } |
Select-Object FullName, LastWriteTime
Pass: Path matches expected_nwjs_profile in map—not a retired product name.
Fail: Two profile folders exist → continue to Step 2.
Outbound references: NW.js manifest name, Construct Storage plugin, MDN localStorage.
Step 2 — Lock package.json name before re-export
In your NW.js export output (Construct generates this on publish):
{
"name": "yourstudio-festdemo-2026",
"main": "index.html",
"window": {
"title": "Fest Demo 2026"
}
}
| Rule | Why |
|---|---|
name never changes after freeze without migration |
Drives %LOCALAPPDATA%/<name>/ profile root |
title may differ for store branding |
Window title ≠ profile folder |
Same name on internal and fest_demo exports |
gate4_branch_save_path_match requires byte match |
Add to save_path_map.json:
{
"schema": "save_path_map_v1",
"engine": "construct3_nwjs",
"company": "YourStudio",
"game": "FestDemo2026",
"nwjs_profile_name": "yourstudio-festdemo-2026",
"expected_root": "%LOCALAPPDATA%/yourstudio-festdemo-2026/",
"storage_keys": [
{
"slot_id": "demo_progress",
"construct_action": "Storage set item",
"key": "SaveSlot",
"forbidden_substrings": ["internal_playtest", "preview"]
}
],
"save_format_version": 1
}
Step 3 — Diff receipts and maps between branches
Before promoting fest_demo:
fc /n release-evidence\construct\internal\save_path_map.json `
release-evidence\construct\fest_demo\save_path_map.json
Extend construct_nwjs_save_receipt_v1.json:
{
"schema": "construct_nwjs_save_receipt_v1",
"project_version": "0.4.0-freeze-week",
"package_json_name": "yourstudio-festdemo-2026",
"gates": {
"gate3_save_path_map": "pass",
"gate5_nwjs_round_trip": "pass",
"gate4_branch_save_path_match": "pass"
},
"freeze_pass": true,
"build_label": "c3-nextfest-2026-05-24-rc1",
"branch": "fest_demo",
"documented_storage_key": "SaveSlot",
"verified_root_note": "%LOCALAPPDATA%/yourstudio-festdemo-2026/"
}
Block promotion if gate4_branch_save_path_match is not pass or package_json_name differs between branches.
Step 4 — Re-export NW.js fest profile and upload depot
- Open Construct Windows desktop (NW.js) export preset for fest—not HTML5-only.
- Confirm
package.jsonnamematches map before export button. - Run Gate 5 round-trip: save → quit exe → relaunch → load on same profile folder.
- Upload to
fest_demodepot only—do not reuse playtest manifest. - Log
build_labelin BUILD_RECEIPT.
Step 5 — One-time migration for early fest testers
If an old profile folder already shipped:
// Pseudologic — On start of Title layout, once per install
// Use Construct Storage actions; detect legacy via separate key probe
if (Storage.Get("SaveSlot") == "" && LegacyProfileHasData()) {
Storage.Set("SaveSlot", LegacyProfileRead());
Storage.Set("save_migrated_from_profile", "internal_playtest");
}
Ship player-facing note: first launch after patch may import progress—or reset if corrupt. Align store FAQ save claims.
Step 6 — Installed Steam smoke (Gate 6)
Run S4–S6 from Wednesday demo build smoke on the Steam-installed NW.js build. Editor preview and itch browser tab do not satisfy this step.
Working dev path (proof table)
| Check | Command / artifact | Pass signal |
|---|---|---|
| Profile name locked | package.json in fest zip |
name equals nwjs_profile_name in map |
| Map parity | fc save_path_map.json internal vs fest |
No differences |
| Storage round-trip | Gate 5 log | Same folder after relaunch |
| Branch gate | construct_nwjs_save_receipt_v1.json |
gate4_branch_save_path_match: pass |
| Installed smoke | Wednesday S4–S6 on Steam build | Save slot count unchanged |
| BUILD_RECEIPT row | save_path column |
Hash of map + package_json_name |
Attach evidence under release-evidence/construct/<branch>/ beside sheet freeze receipt outputs—determinism and persistence are separate gates.
Verification checklist
- [ ]
save_path_map.jsonidentical on internal andfest_demoevidence folders. - [ ]
package.jsonnameunchanged since freeze week (or migration shipped). - [ ] No retired product name in verified
%LOCALAPPDATA%path on installed Steam build. - [ ]
gate4_branch_save_path_match: passin fest receipt. - [ ] Gate 5 NW.js round-trip passes twice on fest
build_label. - [ ] Gate 6 installed smoke GREEN.
- [ ] Wednesday metadata diff includes
save_pathcolumn. - [ ] Playtest and fest use different Storage keys if both live—isolation playbook.
Prevention
- Treat branch promotion as “re-run Gates 3–6,” not “copy depot ID.”
- Add
package.jsonnameto Monday scene inventory beside event sheet order. - Ban branch-specific Storage keys without documented migration—grep
SaveSlotvariants in events. - Add CI job: fail if fest
save_path_map.json≠ internal orpackage_json_namedrifts. - Pair with Defold save root help and Ren'Py label freeze help in multi-engine studios.
Troubleshooting
| Symptom | Fix |
|---|---|
| Browser OK, Steam reset | Re-run Step 1 on installed NW.js; lock package.json name |
| Two AppData folders | Old profile from rename—migration or accept reset |
| Key exists, load empty | Read key ≠ write key in event sheet |
| Only fest branch fails | Wrong depot promoted; verify build_label in-game |
| Playtest OK, fest bad | Separate playtest_save_v1 vs demo_save_v1 keys |
| Freeze receipt pass, players fail | Receipt from browser test, not NW.js exe |
| RNG replay OK, saves fail | Sheet freeze ≠ save path—run both gate sets |
FAQ
Is this the same as GDevelop preview-folder drift?
Same branch promotion class—GDevelop save-path help uses Storage extension paths; Construct uses NW.js package.json name + localStorage profiles.
Does the seven-day sheet freeze cover saves?
The Construct NW.js freeze challenge locks RNG order; add gate4_branch_save_path_match for persistence before Steam upload.
Can I change the window title without breaking saves?
Yes—window.title can differ from name. Only name moves the AppData profile.
Does Steam Cloud fix this?
Only if implemented and FAQ-accurate. Default Storage is local—document canonical path in save_path_map.json.
Whisper API or playtest VOD issues?
Unrelated lane—see playtest triage blog. Save-path gates still block fest promotion when progress resets.
Related links
- 12 Free Construct 3 NW.js Save-Path and AppData Resources (2026)
- 7-Day Construct 3 Event-Sheet Order Freeze Challenge
- GDevelop Steam Demo Preview Save Path After Promotion
- 12 Free Defold GDevelop Ren'Py Save-Path Audit Templates
- Mismatched Save Slot Labels After Branch Promotion
- Wednesday Demo Build Smoke Ritual
- Steam Playtest vs Fest Demo Isolation Playbook
- NW.js manifest format
Prove saves on the Steam-installed Construct NW.js exe—browser preview is not your fest demo.