Your First Construct 3 Roguelike RNG Seed Ledger in One Evening - 2026 Beginner Pipeline
You shipped a browser roguelite to itch. A player refreshed mid-run, reopened the tab, and posted “my build changed—refund” in your comments. You did not change balance that night. You also never wrote down which random() calls ran in which tick order after the refresh restored layout state but not RNG state.
H2 2026 Steam roguelite density means players compare your browser demo to Godot and Phaser titles that already document floor epochs and seed discipline. Construct 3 teams are not exempt because the engine uses event sheets instead of scripts. Nondeterminism after browser refresh is the smallest honest failure mode to fix before you wrap the same project in NW.js for a Steam demo.
This Tutorials & Beginner-First guide builds a seed ledger in one evening: a JSON file that records run identity, pinned tick groups, and a replay checklist you can attach to BUILD_RECEIPT culture before October fest traffic.
Non-repetition note: Blog-Planner refilled a 12-pitch backlog after seventy-one escape-hatch passes dominated by Steam metadata checklists. This is backlog High #1—Construct determinism, not another FAQ-line parity post. No existing URL owns construct 3 rng seed ledger intent.
Why this matters now (May 2026)
- Roguelite shelf pressure — Players expect same seed, same run after refresh; “lost run” reports spike on itch HTML5 demos during May–October fest cycles.
- Browser tab discard — Laptops discard background tabs; returning players re-enter with partial state unless you serialize RNG + sheet order explicitly.
- NW.js Steam path in Q3–Q4 2026 — Desktop export surfaces replay bugs that five-minute editor tests miss; seed ledger is cheaper than post-launch sheet archaeology.
- Cross-engine expectations — Teams reading Godot floor coordinators and Phaser chunk streaming will judge Construct demos by the same determinism vocabulary.
- Partner and playtest discipline — Playtest feedback tools need a
seed_idcolumn; without a ledger you cannot reproduce “floor 4 loot wrong” rows.
Direct answer: Create rng_seed_ledger.json, pin one RunRNG group at the top of the event sheet, log every random() surface you use for gameplay, and pass a three-step replay test (fresh run, mid-run save, refresh resume) before promoting a demo build.
Who this is for and what you get
| Audience | You will be able to… |
|---|---|
| Beginner on first Construct roguelite | Explain why refresh broke a run in plain language |
| Solo dev using event sheets | Pin RNG without rewriting the game in JavaScript |
| Producer | Attach seed_id to playtest forms and crash folders |
| Engineer shipping NW.js | Export a receipt JSON partners can replay-verify |
Time: ~3–4 hours first evening; 20 minutes per weekly demo promotion after.
Prerequisites: Construct 3 project with at least one procedural floor (loot table, enemy spawn table, or room pick from random()), willingness to use Local Storage or a simple JSON download for saves.
Outbound authority: Construct 3 manual — Saving games and System expressions — random for expression truth; verify against your installed build.
What you will have after one evening
release-evidence/rng/rng_seed_ledger.jsonwith stable schema versionRNG_SEED_LEDGER.mdexplaining replay steps for humans- One pinned event group
RunRNGdocumented at sheet top - Inventory of every gameplay
random()with sheet + line notes rng_replay_receipt_v1.jsonfrom tonight’s proof pass- Screenshot of ledger + Construct debugger RNG column after refresh test
Beginner path (first 90 minutes)
- Create folder
release-evidence/rng/. - Copy the ledger schema below into
rng_seed_ledger.json. - Add a On start of layout action: set global
RunSeedfromfloor(random(0, 999999999))once inside groupRunRNG. - Replace loot spawns that call bare
random()withrandom(RunSeed + LootTableSalt + item_index). - Run Test A (below)—if loot changes after F5 refresh, stop and fix before Test B.
Do not rewrite every enemy AI tonight. Do document every remaining bare random() as UNAUDITED in the ledger.
Developer path (fest prep stack)
- Join ledger
run_idto BUILD_RECEIPTbuild_idin upload notes. - Add
seed_idcolumn to playtest CSV exports from 18 playtest tools. - Gate demo promotion on
rng_replay_receipt_v1.json→replay_pass: true. - Cross-check NW.js export uses same project version string as ledger
project_version. - File replay failures in 5-day crash log challenge folders by weekday.
Evening overview (five blocks)
| Block | Minutes | Output |
|---|---|---|
| 1 — Ledger schema | 25 | rng_seed_ledger.json + README |
| 2 — Pin RunRNG group | 45 | Sheet screenshot + group list |
| 3 — random() audit | 60 | random_surfaces[] populated |
| 4 — Save + refresh tests | 50 | Three test results logged |
| 5 — Receipt + proof | 30 | rng_replay_receipt_v1.json |
Stop when Test C (browser refresh mid-run) shows identical loot on floor 1 after resume.
Block 1 — Seed ledger schema (copy exactly)
Create release-evidence/rng/rng_seed_ledger.json:
{
"schema": "rng_seed_ledger_v1",
"project_version": "0.4.2-demo",
"engine": "construct-3",
"runs": [
{
"run_id": "run-2026-05-22-001",
"run_seed": 4815162342,
"floor_index": 1,
"tick_group_order": ["RunRNG", "WorldSim", "Combat", "UI"],
"random_surfaces": [
{
"id": "loot_table_floor_1",
"sheet": "Game",
"event": "On enemy killed",
"expression": "random(RunSeed + 101 + LootIndex)",
"audited": true
}
],
"notes": "First evening pass — UI shake still UNAUDITED"
}
]
}
Field dictionary
| Field | Meaning |
|---|---|
schema |
Bump when you add required keys—partners parse semver |
project_version |
Match NW.js export label and itch build tag |
run_id |
Stable string you give playtesters |
run_seed |
Integer set once per run in RunRNG |
tick_group_order |
Top-to-bottom group names on main sheet |
random_surfaces[] |
Every gameplay RNG touchpoint |
audited |
true only after salt + seed formula reviewed |
Honest limit: The ledger does not fix floating-point drift across browsers if you mix sin/cos physics with RNG—note physics_mode in notes when applicable.
Block 2 — Pin the RunRNG group (event sheet order)
Construct runs event sheets top to bottom each tick. If UI runs before RunRNG and calls random() for screen shake, you consume RNG sequence before combat rolls.
Pinning rules (document in RNG_SEED_LEDGER.md)
- Create group
RunRNGat the top of the primary gameplay sheet. - Inside: set
RunSeedonce on layout start; setRunNonceto 0. - Disable “Active on start” on experimental groups until audited.
- Never call
random()inside Every tick unless wrapped with documented salt. - When adding floors, append groups below
WorldSim, never aboveRunRNG.
Snippet-friendly pattern (event sheet logic)
Use a global number RunSeed and increment RunNonce when you need sub-streams:
On start of layout (RunRNG group)
→ System: Set RunSeed to floor(random(0, 999999999))
→ System: Set RunNonce to 0
On enemy killed (Combat group)
→ System: Add 1 to RunNonce
→ System: Set roll to random(RunSeed + 2000 + RunNonce)
→ Pick loot from table using roll
Why groups matter: A refresh that restores saved floor_index but not RunSeed recreates the classic “different loot” bug. Your save blob must include run_seed + run_nonce + floor_index.
Block 3 — random() audit pass (developer table)
Walk every sheet. For each random( usage, add a row to random_surfaces. Mark unaudited rows explicitly—hiding them guarantees fest-week surprises.
| Sheet | Event | Risk | Fix pattern |
|---|---|---|---|
| Game | On start of layout | High | Move to RunRNG once |
| Game | Every tick | Blocker | Remove or salt + document |
| UI | On button hover | Medium | Use random(100000 + uid) |
| FX | On explode | Medium | Derive from combat RunNonce |
| Menu | On layout start | High | Separate menu seed MenuSeed |
Menu vs run separation
Main menu backgrounds may use MenuSeed independent of RunSeed. Document both in the ledger so playtesters know which seed to report when the bug is “title screen particles wrong” vs “dungeon loot wrong”.
AJAX and async callbacks
If you fetch loot tables from JSON at runtime, do not call random() inside the callback without re-adding RunSeed salt. Async ordering is a common Construct nondeterminism source when callbacks complete in different order after refresh.
Block 4 — Save, resume, and browser refresh tests
Run these three tests on the same itch build or local preview. Record results in RNG_SEED_LEDGER.md.
Test A — Fresh run baseline
- Hard refresh browser (empty cache).
- Start run; note
run_idand first loot drop. - Note three enemy spawn positions.
- Export ledger row for this run.
Pass: Positions and loot match screenshot notes.
Test B — Mid-run local save
- Use Local Storage or downloaded save JSON including
run_seed,run_nonce,floor_index. - Quit tab.
- Reopen; load save.
Pass: Floor index and loot tables unchanged.
Test C — Browser refresh without quitting
- Mid-run on floor 1, press F5 (or mobile reload).
- If you lack auto-resume, load the autosave slot you implement tonight.
Pass: Same loot and enemy layout as pre-refresh.
Fail: Different loot → refresh restored layout globals but not RNG globals—fix save keys before NW.js export.
Autosave minimum JSON (browser)
{
"run_id": "run-2026-05-22-001",
"run_seed": 4815162342,
"run_nonce": 14,
"floor_index": 1,
"project_version": "0.4.2-demo"
}
Store under key roguelite_autosave_v1 via Local Storage plugin. Version the key when schema changes.
Block 5 — Replay receipt (BUILD_RECEIPT cousin)
Create release-evidence/rng/rng_replay_receipt_v1.json after tests:
{
"receipt_type": "rng_replay_receipt_v1",
"project_version": "0.4.2-demo",
"build_id": "optional-from-upload-log",
"tests": {
"fresh_run": "pass",
"mid_run_save": "pass",
"browser_refresh": "pass"
},
"replay_pass": true,
"unaudited_random_surfaces": 2,
"reviewer": "GamineAI Team",
"observed_date_utc": "2026-05-22"
}
Promotion gate: Do not mark demo branch “fest-ready” while replay_pass is false or unaudited_random_surfaces > 0 for gameplay sheets.
NW.js Steam preflight (same evening, 30 minutes)
Browser proof is necessary; desktop proof is not optional for Q3–Q4 2026 NW.js ships.
- Export NW.js with same
project_versionstring. - Run Test C equivalent: close app mid-run, relaunch, resume save.
- Compare
rng_seed_ledger.jsonrun row to desktop log file if you write one. - Note differences in receipt
notes—platform tags belong in release evidence taxonomy.
Deck and Steam wrapper note: This tutorial does not cover Steam Input; pair with Unity Deck graphics only if you hybrid-export—not typical pure Construct paths.
Cross-engine comparison (why players mention Godot)
| Concern | Godot 4.5 pattern | Construct 3 tonight |
|---|---|---|
| Floor transition | Threaded loader + epoch | Pin groups + save floor_index |
| Browser memory | WASM ceiling playbook | Chunk layouts; ledger does not fix OOM |
| Long-session maps | Phaser chunk streaming | Audit random() in procedural spawn |
| Evidence culture | release-evidence/ folders |
release-evidence/rng/ tonight |
Readers searching construct 3 vs godot roguelike 2026 should see complementary pipelines—not engine war.
Common mistakes (beginner-friendly)
- Calling
random()in Every tick for cosmetic jitter—burns sequence; combat rolls shift. - Duplicating On start of layout on multiple sheets—second layout resets seed silently.
- Saving only score and HP—refresh gives new loot on old floor.
- Testing only in editor—editor pause/resume does not equal browser F5.
- Mixing JavaScript SDK random with System random—pick one stream per feature.
- Ignoring preview layout vs game layout—different sheets, different start events.
- Promoting NW.js build without Test C on NW.js—desktop resume bugs differ.
- Skipping
seed_idon playtest forms—you cannot reproduce reports. - Using Date.now() as seed—breaks replay; use intentional
RunSeed. - Leaving template tutorial enemies with stock
random()—audit includes template objects.
Weekly ritual (after evening pass)
Every Wednesday demo smoke (pairs upcoming backlog pitch #12):
- Open latest
rng_replay_receipt_v1.json. - If
project_versionchanged, re-run Test C before branch promotion. - Append new
run_idrows to ledger—never delete old rows (history helps refund triage). - Cross-link weekly note to BUILD_RECEIPT upload log.
Proof table (developer)
| Claim | Evidence artifact | Pass criterion |
|---|---|---|
| Run seed set once | Sheet screenshot RunRNG |
Single layout start setter |
| Loot uses salted roll | random_surfaces[] row |
audited: true |
| Refresh stable | Test C notes | Matching loot screenshot |
| Save stable | Test B save JSON | Keys include seed + nonce |
| Promotion gated | replay_pass |
true before fest branch |
Construct addons and deeper learning
When you need plugins for save slots or debug overlays, start from 20 free Construct 3 addons and the Construct 3 guide hub. If you are new to event sheets entirely, read how to build a game without coding before scaling dungeon generation.
Playtest form one-liner
Add to your Google Form / playtest tool:
seed_id (from pause menu or
run_idin ledger): ___
Players cannot help you reproduce bugs if the form only asks “what happened?”.
Salt table reference (copy to RNG_SEED_LEDGER.md)
| Feature | Suggested salt constant | Added to |
|---|---|---|
| Loot drops | 1000 + item index | RunSeed |
| Room pick | 2000 + floor_index | RunSeed |
| Crit rolls | 3000 + RunNonce | RunSeed |
| Shop prices | 4000 + shop_visit | RunSeed |
| UI cosmetic | 900000 + uid | separate stream |
Salts are not cryptographic security—they are namespace separation inside one PRNG sequence.
Worked example — floor 1 loot table (beginner walkthrough)
Imagine a three-item loot table: Health, Ammo, Curse. Without a ledger you might write:
On enemy killed → Set drop to choose(Health, Ammo, Curse) using random(100)
That works once. After F5 refresh, choose may fire with a different internal sequence because UI particles already consumed random rolls.
Tonight’s fix:
- Create global
LootIndexstarting at 0. - On kill: add 1 to
LootIndex. - Set
rolltorandom(RunSeed + 1000 + LootIndex). - Map ranges: 0–49 Health, 50–79 Ammo, 80–99 Curse.
- Log the row in
random_surfaceswithaudited: true.
Run Test C: kill three enemies, note drops, refresh, reload autosave, kill three enemies again. Drops should match the pre-refresh sequence if LootIndex restored correctly.
Debugger checklist (Construct 3)
Open the debugger while paused:
- Confirm
RunSeedis not zero after layout start. - Confirm
LootIndexincrements only on kill, not on tick. - Watch for duplicate On start of layout firing when returning from pause menu—add a
RunInitializedboolean guard.
Screenshot the debugger with RunSeed, LootIndex, and floor_index visible; store in release-evidence/rng/screenshots/.
Evidence folder layout (solo team)
release-evidence/rng/
rng_seed_ledger.json
rng_replay_receipt_v1.json
RNG_SEED_LEDGER.md
screenshots/
test-c-pass-2026-05-22.png
weekly/
2026-W21-notes.md
This mirrors release evidence taxonomy so partners recognize the folder in diligence ZIPs.
Pairing with the seven-day event-sheet freeze challenge
Backlog pitch #2 (seven-day freeze challenge) builds on tonight’s ledger. Order of operations:
- Tonight — ledger + Tests A–C.
- Next week — freeze sheet order for seven days before NW.js manifest preflight.
- Before Steam — combine receipt + BUILD_RECEIPT in upload notes.
Skipping the ledger and jumping straight to freeze week produces beautiful documentation of a still-nondeterministic game.
When to escalate to JavaScript scripting
Event sheets suffice for many roguelites. Escalate to scripting when:
- You need deterministic replay files (input logs) for esports or speedrun communities
- You port the same ruleset to a second engine
- You implement networked co-op with server authoritative RNG
Until then, ledger + pinned groups beat a half-finished port.
Legal and store copy boundary
Determinism fixes gameplay truth; it does not replace store FAQ parity or wishlist truth audits. If refresh bugs stop but trailer promises co-op, refunds continue—tag those store-copy in refund dashboards.
Key takeaways
- H2 2026 roguelite players punish refresh drift on itch and Steam demos.
- Build
rng_seed_ledger.jsonin one evening—not after NW.js mystery bugs. - Pin
RunRNGat the top of the event sheet; document group order. - Audit every gameplay
random(); mark unaudited rows honestly. - Pass Tests A–C before fest branch promotion.
- Emit
rng_replay_receipt_v1.jsonbeside BUILD_RECEIPT culture. - Include
run_seed+run_noncein every save blob. - Add
seed_idto playtest forms tonight. - Cross-link Godot and Phaser posts for engine comparison queries—not competition.
- Backlog #1 restores Tutorial category after metadata escape-hatch streak.
- 11 backlog pitches remain—Blog-Planner refill succeeded; keep consuming backlog.
- Construct teams can ship determinism without leaving event sheets.
FAQ
Does Construct 3 guarantee deterministic runs across browsers?
No engine guarantees that for all features. You document RNG surfaces, pin order, and prove replay on your scopes.
Is random() seeded automatically on layout start?
No—unless you set it. Default behavior is why refresh tests fail.
Should I use the Advanced Random plugin?
If already integrated, treat it as another random_surfaces row with documented seed API. Do not mix silently with System random().
What about multiplayer?
This pipeline is single-player browser/desktop focused. Server authoritative RNG needs a different tutorial.
How does this relate to daily run leaderboards?
Use a daily salt in ledger notes (daily_seed_2026-05-22) shared by all players that day—separate from per-run RunSeed.
Can I skip NW.js if I stay on itch?
Yes, but run Test C on the same embed players use—GX.games and itch iframes differ.
How many random() rows before I ship?
Zero UNAUDITED rows on gameplay sheets for fest demos; cosmetic UI may wait one week if documented.
Where does AI loot generation fit?
If AI picks table indices, log the index in the ledger row, not model temperature—temperature is not replayable.
What file goes to partners?
rng_replay_receipt_v1.json + redacted ledger excerpt—no need to export entire project.
Does this replace save-format semver?
No—when you add modifiers mid-run, plan the backlog save-format migration pitch; ledger complements semver.
Conclusion
A roguelike without a seed ledger is asking players to trust vibes. One evening gives you run_id, pinned groups, salted rolls, and refresh proof you can attach to the same evidence folders you already use for builds and refunds.
Create release-evidence/rng/ tonight. Pin RunRNG. Run Test C until it passes. Export the receipt before you tell Discord the demo is fest-ready.
Next reads: Construct 3 guide, Godot floor coordinator beginner pipeline, and Phaser H2 roguelite streaming playbook.
Ninety-minute sprint (condensed evening)
| Minute | Task |
|---|---|
| 0–15 | Create rng/ folder + ledger JSON |
| 15–35 | Pin RunRNG group + layout seed |
| 35–55 | Audit top five random() calls |
| 55–75 | Test C refresh attempt |
| 75–90 | Write receipt + screenshot |
Sprint version still beats shipping an unlogged demo.
Integration with refund and crash signals
When refund dashboards tag gameplay with language like “lost my run,” check whether replay_pass was false on that build_id. Correlation turns angry comments into ledger tasks—not moral debates.
SEO and discovery note
This tutorial targets construct 3 roguelike rng seed and construct 3 determinism browser refresh queries—distinct from Steam metadata parity so searchers find engine pipeline content for no-code roguelites.
Stretch goals (if you finish before four hours)
- Add pause-menu display of
run_idcopied to clipboard for playtesters. - Wire
replay_passinto a simple NW.js pre-export event that blocks export when false. - Draft one row in backlog #5 case study format from your own Test C notes—real timestamps, no invented revenue.
- Read Godot WASM memory ceiling if your Construct layout also grows unbounded on long runs—RNG fixes do not replace memory discipline.
These stretch items are optional tonight; the ledger and Test C pass are not optional before you call the demo fest-ready.
Found this useful? Bookmark the Construct 3 guide and share the article with a teammate who ships event-sheet roguelites to itch this month—determinism bugs are faster to fix in pairs when one person runs Tests A–C while the other screenshots the debugger.