We Recovered from Non-Deterministic Replay After Browser Refresh - 2026 Case Study
This is a synthesized case study—a field pattern seen across May–October 2026 fest traffic on itch HTML5 roguelites, especially teams using Construct 3 event sheets. It is not a named studio turnaround story. There are no invented revenue figures, no fake Discord quotes, and no made-up wishlist 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 “lost my run after refresh.”
Pair this narrative with the RNG seed ledger tutorial, seven-day sheet freeze, save semver playbook, and sixteen debugging tools so you have procedure plus story.
Non-repetition note: Construct posts already taught how to fix determinism. This case study describes what broke in the wild and how long recovery took when teams skipped browser tests—not another tutorial step list.
Why this matters now (May–October 2026)
- itch embed + tab discard — Players F5 or return from Discord; partial state restores without RNG globals.
- “Lost run” language — Comments rhyme with refund rows tagged
gameplayin refund dashboards—but root cause is serialization + sheet order, not balance. - Editor-green lies — Construct Debugger passes while Tool 7 refresh protocol fails on real itch origin.
- NW.js week surprise — Teams that fixed browser only still failed desktop until Gate 6 ran.
- Fest patch pressure — Mid-run saves without save semver amplify refresh bugs after Wednesday uploads.
Direct answer: Recovery required RunRNG pinned first, rng_seed_ledger.json audited, autosave keys including run_seed + run_nonce, Test C pass on itch URL, then rng_replay_receipt_v1.json before the next demo promotion.
Beginner quick start — what recovered means here
Recovered in this pattern means:
- Reproduced loot drift on itch with F5 mid-floor—not only in editor.
- Fixed tick order and save payload so post-refresh loot matches pre-refresh.
- Logged receipt JSON and one OBS clip for playtest archive.
- Reduced new “lost run” itch comments for seven days on same
project_version(qualitative, not a fabricated %).
It does not mean zero bugs forever or higher wishlists—only that refresh determinism stopped being the top comment theme.
Success check: You can demo Test C live to a teammate without explaining “maybe the browser lied.”
The player-facing failure (typical signals)
| Channel | Typical wording |
|---|---|
| itch comment | “Refreshed and my build changed” |
| playtest form | “Loot wrong after tab back” |
| refund language | “Lost progress / run invalid” |
| stream clip | Same seed verbally, different drops on screen |
Internal team language before fix: “Cannot reproduce” — because repro happened on itch, not QA’s editor shortcut.
Starting state (what was wrong)
| Layer | Symptom |
|---|---|
| Event sheets | UI_Shake group above RunRNG; Every-tick random() |
| Globals | RunSeed set on two layouts |
| Saves | Local Storage had floor but not run_nonce |
| Ledger | No random_surfaces[]—audit unknown |
| Process | No refresh test in upload checklist |
| Playtest CSV | Missing seed_id column |
Gameplay balance was fine. Trust was not.
Timeline (five working days — pattern timing)
| Day | Focus | Output |
|---|---|---|
| D1 | Repro on itch | Screen recording + fail log |
| D2 | Pin RunRNG + inventory | sheet_inventory_v1.json |
| D3 | Ledger + salt fixes | rng_seed_ledger.json updated |
| D4 | Save keys + semver label | save_format_semver in pause UI |
| D5 | Test C ×3 + receipt | rng_replay_receipt_v1.json |
Teams with prior seed ledger work recovered in two days; greenfield teams took five.
Hour-by-hour D1 (reproduction discipline)
| Step | Action |
|---|---|
| 1 | Open published itch URL—not Construct preview |
| 2 | Start run; note three loot drops |
| 3 | F5; resume via autosave |
| 4 | Compare drops—record pass/fail |
| 5 | Export Local Storage JSON (Tool 6) |
If fail: stop blaming players—proceed D2.
D2–D3 — sheet and RNG fixes (synthesized)
Sheet order fix: Move RunRNG to top per freeze challenge Day 2. Disable Active on start on UI_Shake.
RNG fix: Replace bare random(100) loot with random(RunSeed + 1000 + LootIndex); increment LootIndex on kill only.
Ledger fix: Add fourteen random_surfaces rows; mark two cosmetic streams audited with salt 900000+uid.
No new features shipped during D2–D5—scope freeze mattered.
D4 — save payload fix
Autosave blob before:
{
"floor": 3,
"playerHP": 40,
"lootArray": ["dagger", "potion"]
}
After (semver playbook):
{
"save_format_semver": "2.0.0",
"run": {
"run_seed": 88112233,
"run_nonce": 12,
"floor_index": 3
},
"inventory": {
"loot_manifest": [
{"id": "dagger", "qty": 1},
{"id": "potion", "qty": 1}
]
}
}
Pause menu displayed semver + run_id for playtesters—fed into 18 playtest tools CSV.
D5 — evidence and promotion gate
| Artifact | Path |
|---|---|
| Replay receipt | release-evidence/rng/rng_replay_receipt_v1.json |
| OBS clip | release-evidence/debug/refresh-pass-D5.webm |
| BUILD_RECEIPT note | replay_pass=true; project_version=0.6.1-refresh-fix |
Promotion rule: no itch devlog “balance patch” until replay_pass: true on staging URL.
Symptoms vs root causes
| Player symptom | Root cause | First fix |
|---|---|---|
| Different loot after F5 | Missing run_seed in save |
D4 payload |
| Random feels “cursed” | UI tick consuming RNG | D2 group order |
| Only on itch | Wrong origin tested | D1 itch URL |
| After Wednesday patch | semver drift | Migration golden |
| Desktop differs | NW.js not tested | Freeze Gate 6 |
What we deliberately did not do
- Rewrite combat in JavaScript mid-fest
- Blame itch hosting in public replies without evidence
- Ship “RNG fix” without receipt JSON
- Merge contractor sheet reorder without inventory bump
Communication fix (itch + devlog)
Before (harmful): “Cannot reproduce—works on our machine.”
After (useful): “Build 0.6.1-refresh-fix pins run seeds in saves. If loot changes after refresh on 0.6.0, send run_id from pause menu. We fixed sheet order + save keys—details in patch notes.”
Linked demo patch notes template even for browser-only SKU—players read patch tone as trust.
Refund dashboard crosswalk
After fix, new refund rows were tagged:
| Tag | When |
|---|---|
rng-drift |
Pre-fix builds only |
store-copy |
Trailer mismatch (separate) |
gameplay |
Real combat bugs |
Weekly refund CSV review showed rng-drift cluster stops on builds ≥ 0.6.1-refresh-fix—we report qualitatively, not with invented percentages.
Phaser/Godot cousins (same fest season)
| Engine | Parallel failure | Construct fix analogue |
|---|---|---|
| Phaser | Tab OOM / white screen | Chunk streaming |
| Godot | WASM heap | Floor epoch |
| Construct | Refresh loot drift | Seed ledger + sheet pin |
Same May–October 2026 pressure—different surface.
Proof table (auditors)
| Claim | Evidence |
|---|---|
| Repro existed | D1 fail log + clip |
| Fix targeted RNG/save | Ledger + semver diff |
| Refresh stable | Receipt replay_pass: true |
| Process change | Upload checklist v2 |
| No silent zeros | Fail-closed overlay shipped |
Playtest form upgrade (after recovery)
Required fields added:
build_id/project_versionrun_idsave_format_semverrefresh_test_pass(Y/N)- Optional: Local Storage export
Issues citing Tool 7 from debug listicle closed faster.
NW.js epilogue (week two)
Browser fix shipped D5. Gate 6 caught desktop resume bug—save path differed from Local Storage. Second mini-cycle (two days) aligned NW.js keys with browser schema. Lesson: case study “recovered” on itch ≠ Steam-ready.
Honest limits of this pattern
- Does not fix unreadable pixel fonts (backlog art post)
- Does not replace store truth audits
- Does not prove long-session memory safety on huge tilemaps
- Synthesized—your timestamps and comment volume will differ
If you are in D1 right now
- Stop arguing with the latest itch comment.
- Run Tool 7 on published URL tonight.
- If fail, start seed ledger evening before feature work.
- Schedule freeze week before next NW.js export.
Key takeaways
- Browser refresh is a first-class test—not optional for HTML5 roguelites.
- RunRNG pin + seed ledger fix most “lost run” Construct reports.
- Save semver must include
run_seedandrun_nonce. - Five-day recovery pattern when starting from zero discipline.
- No invented metrics—qualitative comment reduction + receipt proof.
- Case study narrates Construct cluster—not duplicate tutorial steps.
- Pair with itch SKU discipline.
- 6 backlog pitches remain after this pass.
- NW.js needs second verification week.
- Refund tags must separate
rng-driftfromstore-copy.
FAQ
Is this a real studio?
No—synthesized pattern from common 2026 fest reports.
Construct only?
Story is Construct-flavored; Godot/Phaser need their surfaces.
Can we skip ledger if we freeze sheets?
No—ledger proves audit completeness.
What if refresh passes but loot feels wrong?
Balance issue—separate tag from rng-drift.
Do players deserve refunds for pre-fix builds?
Policy decision—document fix build in patch notes.
How to cite this post internally?
“Follow refresh recovery case study artifact list.”
Conclusion
“Lost my run after refresh” is often engineering debt, not player malice. The recovery pattern is boring: pin groups, ledger randomness, semver saves, itch Test C, receipt JSON.
You do not need a postmortem podcast—you need Tool 7 on the real URL and a rng_replay_receipt before the next devlog promises fairness.
Next reads: 16 debugging tools, seed ledger tutorial, and playtest feedback listicle.
RESUBMISSION-style note for demo uploads (template)
When promoting fix build after this pattern, paste into devlog:
Build 0.6.1-refresh-fix — Run seeds and loot rolls now persist across browser refresh. Report issues with run_id from pause menu. Evidence:
rng_replay_receipt_v1.jsonin our QA packet.
Same tone as partner resubmission discipline—players treat it as seriousness.
SEO and discovery note
Targets lost run after refresh roguelite and itch html5 determinism fix 2026—case study intent separate from tutorial and listicle URLs.
Evidence folder after recovery (final tree)
release-evidence/
rng/
rng_seed_ledger.json
rng_replay_receipt_v1.json
sheet_inventory_v1.json
saves/
save_schema_v1.json
golden/
debug/
refresh-pass-D5.webm
browser-refresh-fail-D1.log
04-playtest/
feedback-log-refresh-fix.csv
Matches release evidence taxonomy so partners browsing ZIPs recognize structure.
D2 deep dive — the UI_Shake regression (synthesized)
The most common hidden culprit in this pattern is a cosmetic group running before combat RNG. The team added screen shake on hit stop during a polish week. Shake used random(360) every tick for angle variance. Combat loot used the next random() values in the global sequence.
Player experience: First kill after refresh rolled different loot because the tick count at kill moment did not match pre-refresh tick count—even when RunSeed was correct in Local Storage.
Fix: Move shake to triggered one-shot with random(900000 + uid) documented in ledger. Disable shake group Active on start.
Lesson for contractors: Polish tasks need ledger rows before merge.
D3 deep dive — duplicate layout start events
Construct projects often have Menu and Game layouts each firing On start of layout. Second layout accidentally reset RunSeed when returning from pause menu—players perceived “new run” mid-floor.
Detection: Debugger breakpoint on any Set RunSeed action—count hits per session.
Fix: RunInitialized boolean—only set seed when false; set true after first assignment.
Local Storage forensics (D1 attachment)
When playtesters attach storage export, compare keys:
| Key present | Meaning |
|---|---|
roguelite_autosave_v1 only |
Legacy schema |
Missing run_nonce |
Refresh drift likely |
| Two autosave keys | Migration incomplete |
Use Tool 9 jq from debug listicle to diff pre/post fix exports—evidence for GitHub issue closure.
BUILD_RECEIPT join (upload discipline)
upload_log.csv row after fix:
build_id=demo-itch-0.6.1-refresh-fix-20260522,branch=itch-demo,notes=replay_pass=true;rng_receipt=release-evidence/rng/rng_replay_receipt_v1.json;construct_debug_stack=v1
Future-you identifies which binary introduced replay discipline without opening Construct project.
Seven-day comment monitoring (qualitative protocol)
We did not publish “78% fewer complaints.” We tracked:
| Week | New itch comments mentioning refresh/loot | Action |
|---|---|---|
| Pre-fix | Cluster daily | D1–D5 sprint |
| Post-fix w1 | Sparse | Monitor only |
| Post-fix w2 | Single edge case | Open Tool 7 ticket |
| Fest week | Any spike | Block promotion |
Edge case in w2 was semver 2.0.0 mid-run migration—fixed with ruleset freeze, not RNG pin.
Streamer / clip review protocol
When a clip shows “different loot,” require:
- Visible
project_versionon pause overlay (added D4) - Comment timestamp vs devlog patch time
- OBS vs direct itch—embed can differ; still must pass Tool 7 on embed URL
Prevents endless debate with content creators during fest visibility spikes.
Two-storefront note
Teams with itch + Steam demo must run recovery on each channel. This case study’s D1–D5 pass targeted itch HTML5 first—Steam NW.js followed Gate 6. Do not assume one fix propagates.
Incident retrospective questions (team workshop)
Ask in 45 minutes—no blame language:
- Why was Tool 7 missing from upload checklist?
- Who owns ledger updates on balance patches?
- Do playtest forms require
run_id? - Is NW.js scheduled before fest branch?
- What semver policy applies to Wednesday tweaks?
Outputs one checklist row in Wednesday smoke ritual backlog when published.
Comparison to partner hash case study
| Dimension | Hash mismatch case | This refresh case |
|---|---|---|
| Surface | Partner ZIP bytes | Player runtime trust |
| User | Reviewer laptop | itch tab |
| Fix type | Manifest paths | RNG + save |
| Receipt | SHA256 cold | rng_replay_receipt |
| Timeline | 72 hours | ~5 days typical |
Both are evidence culture stories—different folders under release-evidence/.
Developer checklist (copy for PR template)
- [ ]
sheet_inventory_v1.jsonmatches editor order - [ ]
random_surfacescount = Find Results count - [ ] Test C pass on itch staging URL (attach log)
- [ ]
rng_replay_receipt_v1.jsonupdated - [ ] Pause menu shows semver + run_id
- [ ] NW.js Test C if desktop SKU ships
- [ ] Devlog cites build id—not “fixed RNG” vaguely
Producer-facing one-pager
Refresh determinism means the same saved run loads the same loot after F5. We prove it with
rng_replay_receipt_v1.jsonbefore each demo promotion. Playtesters: send run_id from pause menu.
Paste into producer Notion—links full case study URL for engineers.
When this case study does not apply
- Single-scene arcade score chasers
- Games without saves mid-run
- Pure local executables with no browser SKU
- Teams already on Godot with floor epoch receipts—read Godot playbooks instead
Stretch goals after recovery
- Automate Tool 14 Playwright on itch staging.
- Add in-game replay verify debug command (QA flavor).
- Publish comparative Godot vs Construct floor post from backlog #3.
Found this useful? Send to a teammate who said “works in editor” last week—the case study is the argument for Tool 7 tonight.
Anti-patterns we saw in the same comment threads
| Anti-pattern | Why it fails |
|---|---|
| “Seed displayed in menu” without save | Display ≠ persistence |
| Daily seed changes mid-run | Ruleset not frozen |
| Blaming itch CDN | Drift reproduces offline |
| Hotfix random balance same day | Confounds audit |
| Closing tickets without receipt | Regression returns |
Ninety-minute “are we in this case study?” audit
| Minute | Question |
|---|---|
| 0–15 | Does F5 change loot on itch? |
| 15–30 | Is RunRNG first on sheet screenshot? |
| 30–45 | Does storage JSON include run_seed? |
| 45–60 | Is there a ledger file in repo? |
| 60–90 | Write pass/fail in qa-voice-fallback sibling qa-refresh.log |
If first step fails—you are in this case study. If all pass—move on to other backlog topics (fonts, economics, etc.).
rng_replay_receipt_v1.json (example after D5)
{
"receipt_type": "rng_replay_receipt_v1",
"project_version": "0.6.1-refresh-fix",
"build_id": "demo-itch-0.6.1-refresh-fix-20260522",
"tests": {
"fresh_run": "pass",
"mid_run_save": "pass",
"browser_refresh": "pass"
},
"replay_pass": true,
"unaudited_random_surfaces": 0,
"reviewer": "GamineAI Team",
"observed_date_utc": "2026-05-22",
"notes": "itch embed URL tested; NW.js Gate 6 scheduled week 2"
}
Partners rarely ask for this on itch-only demos—but fest publishers increasingly request “offline fairness” narrative; receipt is cheap proof.
Community response template (itch comment reply)
Thanks for the report. If you were on build 0.6.0 or earlier, refresh could change loot—we fixed that in 0.6.1 (pinned run seeds in saves). If you still see drift on 0.6.1, please paste run_id from the pause menu so we can match your save file.
No invented stats—build numbers and run_id only.
Cost of five-day delay (opportunity framing)
| Delay day | Risk |
|---|---|
| D1 skipped | More refund-language rows |
| D2 skipped | Contractor merge worsens order |
| D3 skipped | Ledger lies |
| D4 skipped | Saves still half-migrated |
| D5 skipped | Promoted lying binary |
Five days is shorter than two weeks of comment firefighting during October peak.
Cross-training for non-Construct teammates
Producers and artists can run Tool 7 without opening event sheets:
- Open itch link from checklist.
- Play three minutes.
- F5 once.
- Screenshot loot panel.
- File form with pass/fail.
Engineering fixes; whole team detects.
Link graph (Construct cluster completion)
| Order | Post | Role |
|---|---|---|
| 1 | Seed ledger tutorial | How |
| 2 | Freeze challenge | Week discipline |
| 3 | Save semver | Mid-fest patches |
| 4 | 16 tools | Index |
| 5 | This case study | Why it mattered |
New hires read 5 → 1 if they need motivation before procedure.
Accessibility and refresh
Players using keyboard-only navigation may reload via browser shortcuts more often—refresh bugs hit accessibility-heavy audiences harder. Subtitles showing run_id and semver help support staff assist without video calls.
Final honesty block
GamineAI Team publishes synthesized patterns to reduce repeated support questions across indie teams. Your comment volume, refund rate, and engine version will differ. The artifact list is the portable part—timelines are indicative, not guarantees.
Friday Block 5 line (maintenance hook)
Add to weekly Friday Block 5 notes:
itch_refresh_test=pass/fail; rng_receipt_path=release-evidence/rng/rng_replay_receipt_v1.json
Fifteen seconds prevents promoting a build that only passed editor smoke.
What we would do differently (synthesized retrospective)
Start seed ledger before public itch upload—not after first refund comment. Run freeze week before adding cosmetic polish groups. Treat Tool 7 as a release gate equal to “game boots.”
That ordering would have collapsed this five-day pattern into a single prevention afternoon—and left the team free to argue about balance and art during fest month instead of defending refresh fairness in comment threads.
Reader assignment (optional)
If you lead a micro-team, assign one engineer to run the ninety-minute audit at the end of this post and attach qa-refresh.log to your next BUILD_RECEIPT. You either confirm you are not in this case study—or you start D1 the same day. That single log file ends more debates than another week of editor playtests.