Construct 3 2026 H2 Roguelite Save-Format Migration - Trend Playbook for Daily-Run Semver
You shipped daily seed mode for the October fest push. On Wednesday you added a corruption modifier and bumped the project. Thursday support logs show players with full inventories on paper and empty hands in-game—not cheats, not cloud sync ghosts, but v1 save blobs parsed by v2 event sheets after you renamed instance variables.
H2 2026 roguelite teams on Construct 3 hit this wall more than engine-specific bug trackers admit. Browser Local Storage, NW.js file paths, and itch embeds all cache JSON saves players expect to survive mid-fest balance patches. Without save schema semver and a migration ladder, you are not balancing—you are corrupting runs.
This Trend-Jacking / News Commentary playbook is the Construct counterpart to Phaser chunk streaming and Godot WASM floor epochs—same long-session + live ops pressure, different failure surface: serialized state vs code drift.
Non-repetition note: The Construct arc already published a seed ledger tutorial and seven-day sheet freeze challenge. This URL owns save-format semver and migration—not RNG pin order or NW.js gate calendars.
Why this matters now (May 2026)
- Daily-run live ops — Studios add modifiers, relics, and floor rules between demo uploads; players mid-run do not restart voluntarily.
- Dual-runtime parity — Same JSON must load in browser preview, itch iframe, and NW.js Steam with identical migration outcomes.
- Fest patch velocity — Demo patch notes announce balance; saves must either migrate or fail closed with readable errors—not silent corruption.
- Cross-engine player literacy — Godot teams document floor epochs; Phaser teams document chunk IDs. Construct saves need visible schema version in pause UI.
- Refund language — “Lost my run” spikes when saves parse half-successfully; pair with refund signal dashboards tagging
save-migrationvsgameplay.
Direct answer: Ship save_format_semver in every blob, implement migrate_v1_to_v2 functions you can unit-test in isolation, and refuse load when migration cannot prove integrity—offer abandon run with seed preserved for leaderboard integrity.
The failure mode in one paragraph
Naive pattern: Local Storage stores a JSON string built from ad-hoc globals (playerHP, lootArray, floor) whenever the player quits. Mid-fest you add corruptionStacks and rename lootArray to loot_manifest. Load path does JSON.Parse then assigns variables that no longer exist on the sheet. Construct defaults missing keys to zero or empty—players lose hours without a crash.
What breaks: Not the engine—implicit schema. Migration is an afterthought because editor playtests always start fresh layouts.
Honest limit: Semver saves do not fix unfair balance. They fix trust that a stored run matches the ruleset that wrote it.
Who should read this
- Construct teams running daily seeds or modifier seasons through October 2026 fest traffic
- Devs who passed RNG replay tests but still see post-patch inventory bugs
- Producers scheduling Wednesday balance drops beside metadata sprints
- Engineers exporting NW.js after sheet freeze week
Trend signal vs evergreen advice
| Evergreen truth | 2026 H2 trend twist |
|---|---|
| Version your saves | Expose save_format_semver in pause menu |
| Backup before migrate | Log save_migration_receipt_v1.json per bump |
| Test load after patch | Test mid-run load on itch + NW.js same day |
| Document changelog | Document migration matrix beside patch notes |
Beginner path (schema in one afternoon)
- Wrap all save globals in one object—stop scattering keys.
- Add
"save_format_semver": "1.0.0"on every write. - On load: if semver ≠ current, call one migration function or show “run incompatible—start new daily”.
- Copy template
save_schema_v1.jsonbelow intorelease-evidence/saves/. - Screenshot pause menu showing semver for playtesters.
Success check: You can explain what happens to a Tuesday save when Wednesday’s build adds one modifier—without saying “we will fix later.”
Developer path (migration ladder + receipts)
- Maintain
migrations/README.mdtable: from → to → function name → data loss policy. - Join
save_migration_receiptto BUILD_RECEIPTbuild_id. - Gate fest promotion when open migration failures > 0 on staging keys.
- Store golden saves per semver in
release-evidence/saves/golden/. - Run cold migration on second machine like cold-hash challenge culture.
Save schema contract (Construct 3)
Canonical file: release-evidence/saves/save_schema_v1.json (documentation artifact—not auto-loaded by engine):
{
"save_format_semver": "2.0.0",
"engine": "construct-3",
"project_version": "0.6.0-fest",
"run": {
"run_id": "run-2026-05-22-004",
"run_seed": 4815162342,
"run_nonce": 88,
"floor_index": 3,
"daily_seed": "2026-05-22",
"ruleset_id": "fest-week-19"
},
"modifiers": {
"corruption_stacks": 2,
"daily_modifier_ids": ["mod_corruption_surge_v2"]
},
"inventory": {
"loot_manifest": [
{"id": "health_small", "qty": 1}
]
},
"meta": {
"written_utc": "2026-05-22T18:40:00Z",
"channel": "browser"
}
}
Required top-level keys (policy)
| Key | Purpose |
|---|---|
save_format_semver |
Major breaks inventory layout; minor adds optional keys |
project_version |
Matches NW.js binary label |
run.run_seed |
Ties to RNG ledger |
run.ruleset_id |
Names daily modifier bundle |
meta.channel |
browser vs nwjs for parity audits |
Semver law for micro-studios:
- MAJOR — remove/rename keys, change array item shape, reset inventory interpretation
- MINOR — add optional keys with safe defaults
- PATCH — documentation-only or non-serialized tweaks
Bump MAJOR → run migration or fail closed. Never silently read MAJOR v1 with v2 sheets.
Migration ladder pattern
Document in release-evidence/saves/migrations/README.md:
| From | To | Function | Data loss |
|---|---|---|---|
| 1.0.0 | 1.1.0 | migrate_1_0_to_1_1 |
None — adds ruleset_id default |
| 1.1.0 | 2.0.0 | migrate_1_1_to_2_0 |
Maps lootArray → loot_manifest |
Event sheet migration spine (conceptual)
Use a dedicated SaveMigration group—runs before gameplay assigns parsed JSON to globals:
On load game from storage
→ Parse JSON to object SaveRoot
→ If SaveRoot.save_format_semver < CurrentSemver
→ Call MigrateSave(SaveRoot)
→ If MigrateSave returns OK
→ ApplyGlobalsFromSave(SaveRoot)
→ Else
→ Show overlay "Run incompatible"
→ Offer "Abandon run" / "Copy seed"
Fail closed: If MigrateSave returns false, do not apply partial globals—that produces refund-grade “lost loot.”
Golden save tests
For each semver pair, keep golden/v1_0_0_mid_floor3.json in repo. CI or manual Friday script:
- Load golden into migration function off-device.
- Assert output semver and inventory counts.
- Write
save_migration_receipt_v1.json:
{
"receipt_type": "save_migration_receipt_v1",
"from_semver": "1.0.0",
"to_semver": "2.0.0",
"golden_tests_pass": 3,
"migration_pass": true,
"build_id": "fest-demo-20260522",
"observed_date_utc": "2026-05-22"
}
Daily-run modifier mid-fest (trend core)
Scenario: Tuesday daily seed uses ruleset_id: fest-week-19. Wednesday you ship mod_corruption_surge_v2 increasing stack cap. Players mid-run have saves without corruption_stacks.
Wrong fix: Default missing key to 0 and spawn enemies with new AI—player sees run change underneath them.
Right fix options:
| Strategy | When to use |
|---|---|
| Migrate in place | Add corruption_stacks: 0 with banner “new modifier active next floor” |
| Freeze ruleset on run start | ruleset_id locked at run begin; daily banner only affects new runs |
| Fail closed | Major semver bump; player abandons with seed credit |
Most fest teams should freeze ruleset at run start for demo scope; use migration only for bugfix serialization, not live balance swings.
ruleset_id discipline
- Publish
rulesets/fest-week-19.jsonlisting modifier IDs and semver. - Patch notes link
ruleset_idnot just “balance.” - Playtest form asks
ruleset_id+save_format_semver+seed_id.
Local Storage vs NW.js parity
| Surface | Storage key | Risk |
|---|---|---|
| Browser / itch | localStorage["roguelite_save_v2"] |
Quota eviction |
| NW.js | File path under user data | Path change on reinstall |
| Editor preview | Separate origin | False confidence |
Parity test (mandatory before branch promotion):
- Write save on itch build semver 2.0.0.
- Copy JSON string manually to NW.js test harness (or shared export tool).
- Load on NW.js—inventory and
run_seedmust match. - Log result in
release-evidence/saves/parity/YYYY-MM-DD.log.
NW.js path changes are a 2026 trend because teams test browser-only until Steam week—then blame engine instead of storage split.
Pairing with RNG and sheet freeze
| Layer | Post | This playbook |
|---|---|---|
| Tick order | Sheet freeze challenge | Same project_version string |
| RNG | Seed ledger | run_seed inside save root |
| Saves | Here | save_format_semver |
Promotion gate: do not ship semver 2.0.0 binary while sheet_freeze_receipt and rng_replay_receipt still cite 0.5.0-freeze-week without retest.
Cross-engine comparison (floor epoch vs save semver)
| Engine | State boundary | Construct analogue |
|---|---|---|
| Godot | Floor epoch teardown | Major semver on floor layout change |
| Phaser | Chunk visit bitmap | Serialize visited chunks only |
| Construct | JSON globals | save_format_semver + migration |
The blog backlog includes a Godot vs Construct floor-transition comparative pitch—read Godot threaded loader today if loads hitch; read semver here if saves lie.
Proof table (developer)
| Claim | Evidence | Pass |
|---|---|---|
| Semver visible | Pause UI screenshot | Player can report version |
| Migration tested | golden_tests_pass ≥ 1 |
Receipt true |
| Fail closed | Forced bad blob test | Overlay, no silent zero |
| Channel parity | itch + NW.js log | Matching inventory |
| Patch linked | Patch note cites ruleset_id |
Ops traceability |
Common mistakes
- Bumping only
project_versionwithoutsave_format_semver. - Renaming instance variables without MAJOR bump—Construct does not auto-map.
- Defaulting missing keys to zero—looks like corruption.
- Migrating balance mid-run when marketing promised daily fairness.
- Testing only new runs after patch—mid-run golden saves skipped.
- Separate keys per layout—use one root object per run.
- Ignoring Local Storage quota—silent write fail; no save.
- No abandon-run path—players force-refresh and blame refunds.
- Cloud-less claims while Steam Cloud enabled on main app—see cloud saves parity.
- Skipping receipt—partners cannot verify migration discipline.
Wednesday live-ops ritual (modifier + semver)
Pair with Wednesday demo smoke when it ships; until then:
- Decide: freeze ruleset or MAJOR migrate.
- Update
migrations/README.mdbefore export. - Run golden saves on staging key.
- Update patch note with semver +
ruleset_id. - Add Friday Block 5 line:
save_migration_pass Y/N.
Playtester reporting block (copy to form)
save_format_semver(pause menu)ruleset_id(pause menu)seed_id/run_idchannel(browser / steam / nwjs)- Did you resume mid-run after patch? (Y/N)
Feeds 18 playtest tools CSV columns without inventing studio names.
Outbound references
Verify APIs against your installed Construct build before fest week.
Key takeaways
- H2 2026 daily-run ops break implicit saves when modifiers ship mid-fest.
save_format_semvermust be visible and tested—not buried in code comments.- Migration ladder or ruleset freeze—pick explicitly; do not drift.
- Fail closed beats silent zero defaults on missing keys.
- itch + NW.js parity is a same-day test, not Steam-week hope.
- Pairs with Construct RNG ledger and sheet freeze posts—orthogonal URLs.
- Trend category after Tutorial + Challenge Construct arc.
- 9 backlog pitches remain.
- Golden saves +
save_migration_receipt_v1.jsonmatch site evidence culture. - “Lost run” refunds often need
save-migrationtags—not gameplay triage.
FAQ
Do I need semver if I only ship once?
If you patch during a fest, yes. One-shot jam games can defer—roguelite demos cannot.
Can I store saves in project files?
NW.js can; browser cannot. Document channel in meta.channel.
What about Steam Cloud?
Demo scope may disable cloud; if enabled, align with cloud parity checklist—separate from Local Storage semver.
How is this different from RNG ledger?
Ledger pins random sequence; semver pins serialized state shape.
Should daily seeds reset semver?
Daily run can reset while global profile semver still migrates—split keys: roguelite_run_v2 vs profile_v1.
JavaScript plugin saves?
Same semver rules—one migration entry point.
Player-facing error copy?
“This run was saved under an older festival ruleset. Start a new daily run or copy your seed: __.”
Minimum golden files?
At least one mid-run save per MAJOR bump you ship during fest month.
Construct 4?
Keep engine: construct-3 until migration; bump when exporter changes.
Next backlog read?
ElevenLabs + Ollama fallback architecture pitch on the blog plan—pairs Help-Create voice SDK fixes when that post ships; different cluster from saves.
Conclusion
Mid-fest modifiers are a product decision; save migration is an integrity decision. Construct teams that document semver, test golden saves, and fail closed keep “lost run” posts out of refund dashboards.
Define save_format_semver today. Add one migration function before the next Wednesday balance drop. Log a migration receipt beside your BUILD_RECEIPT.
Next reads: RNG seed ledger, Sheet freeze challenge, and Construct 3 guide.
Ninety-minute semver bootstrap
| Minute | Task |
|---|---|
| 0–15 | Create save_schema_v1.json template |
| 15–35 | Wrap globals into one save root |
| 35–50 | Add pause-menu semver label |
| 50–70 | Write one migrate_1_0_to_1_1 stub |
| 70–90 | Golden save + receipt JSON |
Bootstrap does not replace golden tests before MAJOR bumps—it starts the habit.
SEO and discovery note
Targets construct 3 save format migration and roguelite daily run save version 2026—distinct from RNG and event-sheet order queries.
Evidence folder layout
release-evidence/saves/
save_schema_v1.json
migrations/README.md
golden/
parity/
save_migration_receipt_v1.json
Aligns with release evidence taxonomy so diligence ZIPs stay predictable for partners reviewing fest demos.
Worked migration — lootArray to loot_manifest (MAJOR 2.0.0)
v1.0.0 shape (legacy):
{
"save_format_semver": "1.0.0",
"playerHP": 40,
"floor": 3,
"lootArray": ["health_small", "dagger_rusty"]
}
v2.0.0 shape (current):
{
"save_format_semver": "2.0.0",
"run": { "floor_index": 3, "run_seed": 991122, "ruleset_id": "fest-week-19" },
"inventory": { "loot_manifest": [{"id": "health_small", "qty": 1}, {"id": "dagger_rusty", "qty": 1}] }
}
Migration function responsibilities (pseudo-events)
| Step | Action |
|---|---|
| 1 | Parse JSON; if missing save_format_semver, treat as 0.9.0 legacy |
| 2 | If semver < 1.0.0, fail closed with “pre-fest save” message |
| 3 | migrate_1_0_to_1_1 adds run.ruleset_id default fest-week-18 |
| 4 | migrate_1_1_to_2_0 maps floor → run.floor_index, builds loot_manifest |
| 5 | Validate run_seed present or fail closed |
| 6 | Write migrated object to storage only after all steps pass |
Inventory qty rule: v1 listed duplicate string entries; migration counts occurrences into qty—document that in patch notes so players understand stacked daggers did not vanish.
Player-facing banner (after successful MAJOR migrate)
Ruleset updated for festival week 19. Your run continues; corruption modifiers apply starting floor 4.
Banners reduce refund suspicion compared to silent stat changes.
Local Storage quota and write failures (2026 browser trend)
Browsers cap Local Storage per origin (commonly ~5 MB, varies). Roguelite saves with large loot_manifest arrays approach limits after long runs.
| Symptom | Likely cause | Fix |
|---|---|---|
| Save silently stops | Quota exceeded | Compress manifest; prune history |
| Save works editor not itch | Different origin | Test on real itch URL |
| Intermittent load null | Private mode blocks storage | Detect; show error |
| NW.js works browser fails | Separate keys | Document in patch notes |
Trend discipline: Log meta.bytes on write during internal playtests. If saves exceed 400 KB, split run vs profile keys.
Fest-month patch calendar (example)
| Week | Change type | Semver bump | Player impact |
|---|---|---|---|
| W18 | Bugfix serialize typo | PATCH 1.0.1 | Migrate optional |
| W19 | Add corruption modifier | MINOR 1.1.0 or ruleset freeze | Banner |
| W20 | Rename loot structure | MAJOR 2.0.0 | Migrate or abandon |
| W21 | NW.js path only | PATCH 2.0.1 | Parity test |
Publish calendar fragment in demo patch notes so store copy matches save behavior.
Synthesized pattern — mid-fest “lost loot” (no invented metrics)
Teams report a recurring support pattern during May–October 2026 fest traffic:
- Player resumes mid-run after a Wednesday demo upload.
- Inventory UI shows items; combat drops do not match icons.
save_format_semverwas never bumped whenlootArraybecameloot_manifest.- Fix: MAJOR bump + migration + pause-menu semver label.
- Refund language drops after fail-closed overlay ships instead of silent zeros.
This is the narrative backbone for backlog case study #4—implement semver before you need the case study URL.
Instance variable rename trap (Construct-specific)
Construct serializes by variable names on objects when teams use pick-global / JSON builders tied to names. Renaming PlayerHP to player_hp without semver is equivalent to deleting player health.
Policy: Any global rename touching save payload → MAJOR bump + migration mapping table:
| Old key | New key | Default if missing |
|---|---|---|
PlayerHP |
player_hp |
fail closed |
lootArray |
inventory.loot_manifest |
migrate |
Keep mapping CSV in migrations/field_map_1_1_to_2_0.csv for support staff.
Autosave frequency vs migration risk
| Autosave interval | Risk |
|---|---|
| Every room | More writes; quota pressure |
| Every floor | Balanced for roguelites |
| On quit only | Lose progress; fewer corrupt mid-migrate |
If you autosave every room, debounce migration—do not run full ladder twice per second when semver already current.
Abandon-run UX (fail-closed companion)
When migration fails, offer:
- Copy seed to clipboard (
run_seed+daily_seed). - Start new daily without wiping profile semver.
- Report bug link with semver pre-filled query params.
Never leave a black screen—Construct players assume tab crash, not save policy.
BUILD_RECEIPT join example
upload_log.csv note column:
save_format_semver=2.0.0; migration_receipt=save_migration_receipt_v1.json; ruleset=fest-week-19
Partners reviewing validate-packet scripts can grep semver in notes without opening game repo.
Troubleshooting matrix (support)
| Player report | Check | Fix |
|---|---|---|
| Empty inventory | semver mismatch | Migrate or fail closed |
| Wrong floor | floor vs floor_index |
MAJOR map |
| Seed changed | load order before RunRNG | Sheet freeze |
| Works PC not web | channel key | Parity log |
| After patch only | golden save missed | Add test |
Stretch goals after semver ships
- Export
rulesets/*.jsonwith semver for mod tools. - Add dev-only “force migrate” cheat guarded by
?dev=1on localhost only. - Wire
save_migration_passinto NW.js pre-export likefreeze_pass. - Schedule backlog Construct determinism tools listicle to automate golden save runs.
Found this useful? Pair this playbook with the seven-day sheet freeze the week before you enable weekly modifier drops—order reduces simultaneous variables.
Profile vs run saves (split keys)
Roguelites often need two semver tracks:
| Key | Semver | Contents |
|---|---|---|
roguelite_profile_v1 |
Slow | Unlocks, cosmetics, tutorial flags |
roguelite_run_v2 |
Fast | Floor, inventory, modifiers |
Trend mistake: One blob mixing profile and run—every profile tweak risks migrating active fest runs. Split before October traffic.
Load order: Migrate profile first, then run; if run fails, profile still loads so players can start a new daily without wiping meta progress.
QA script (60-minute pre-upload)
- Install build N-1; start run; save mid-floor.
- Install build N; load save—expect migrate or fail closed.
- Install build N fresh run—expect clean semver write.
- Copy save JSON to NW.js; repeat load.
- File
save_migration_receipt_v1.jsonand attach to BUILD_RECEIPT.
Skipping step 1 is how teams ship migrations that only work on empty saves.
Modifier freeze decision tree
Wednesday balance change?
├─ Touches serialized fields?
│ ├─ YES → MAJOR semver + migration + golden test
│ └─ NO → PATCH only
└─ Adds modifier mid-run?
├─ Freeze ruleset at run start → MINOR or ruleset_id only
└─ Apply mid-run → MAJOR + banner + support template
Post the tree in release-evidence/saves/README.md so producers do not negotiate semver in chat at upload hour.
Community devlog snippet (optional)
Save schema 2.0.0 — mid-run saves from 1.x migrate or prompt a clean daily start. Pause menu shows your semver; report bugs with that number.
One sentence in a fest devlog prevents fifty Discord messages asking whether saves should work across patches. Treat semver like a public API contract for player trust.