Publishing & Deployment Issues May 24, 2026

Construct 3 NW.js Steam Demo Saves to Wrong AppData Path After Fest Branch Promotion - How to Fix

Fix Construct 3 NW.js Steam demos that write saves under the wrong AppData localStorage folder after fest branch promotion. package.json name, save_path_map.json, Storage keys, and construct_nwjs_save_receipt_v1.json.

By GamineAI Team

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.jsondiff 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

  1. The Construct NW.js save-path resource list and sheet-order freeze made save_path_map.json standard—teams promote branches before gate4_branch_save_path_match exists on fest evidence.
  2. Steam fest_demo builds often inherit playtest exports where package.json name still says internal_playtest while the store page shows a new product title.
  3. Browser-first roguelites keep localStorage semantics; NW.js maps them to per-profile folders under AppData—preview origin ≠ packaged exe origin.
  4. 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 SaveSlot unchanged but profile folder renamed after title change.
  • construct_nwjs_save_receipt_v1.json on fest branch missing or stale vs internal.
  • Playtest branch saves persist; fest_public does not (shared key, different package.json name).
  • After game update, players report “save deleted” with no error dialog.

Root causes (check in order)

  1. package.json name changed between internal export and fest export—NW.js creates a new profile; old localStorage invisible.
  2. Construct project display name changed without re-exporting NW.js—events reference old keys; disk path moved.
  3. Branch promotion copied NW.js folder onlysave_path_map.json updated on internal, not fest evidence folder.
  4. Preview vs export tested — Gate 5 passed on browser tab; Gate 6 installed Steam never run on fest_demo.
  5. Hardcoded Storage key per branch (demo_save vs demo_save_preview) — UI shows slot 1 while disk uses slot 2.
  6. Wrong depot promoted — playtest binary on fest branch (see save-slot label case study).
  7. 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%.

  1. Install the build from fest_demo (or local NW.js exe with identical export settings).
  2. Play to first save trigger; note the time.
  3. Open File Explorer → %LOCALAPPDATA% → search for folders modified in the last 10 minutes (often under a name matching package.json name).
  4. If the folder name does not match your save_path_map.json nwjs_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)

  1. Install from fest_demo branch (or fest-profile local export).
  2. Delete documented profile folders listed in old save_path_map.json (backup first).
  3. Launch; trigger save at first checkpoint.
  4. 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

  1. Open Construct Windows desktop (NW.js) export preset for fest—not HTML5-only.
  2. Confirm package.json name matches map before export button.
  3. Run Gate 5 round-trip: save → quit exe → relaunch → load on same profile folder.
  4. Upload to fest_demo depot only—do not reuse playtest manifest.
  5. Log build_label in 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.json identical on internal and fest_demo evidence folders.
  • [ ] package.json name unchanged since freeze week (or migration shipped).
  • [ ] No retired product name in verified %LOCALAPPDATA% path on installed Steam build.
  • [ ] gate4_branch_save_path_match: pass in fest receipt.
  • [ ] Gate 5 NW.js round-trip passes twice on fest build_label.
  • [ ] Gate 6 installed smoke GREEN.
  • [ ] Wednesday metadata diff includes save_path column.
  • [ ] Playtest and fest use different Storage keys if both live—isolation playbook.

Prevention

  1. Treat branch promotion as “re-run Gates 3–6,” not “copy depot ID.”
  2. Add package.json name to Monday scene inventory beside event sheet order.
  3. Ban branch-specific Storage keys without documented migration—grep SaveSlot variants in events.
  4. Add CI job: fail if fest save_path_map.json ≠ internal or package_json_name drifts.
  5. 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

Prove saves on the Steam-installed Construct NW.js exe—browser preview is not your fest demo.