Make progress survive a browser refresh. A real game remembers you.

Close your game right now. Open it again. Is your progress still there? No? That's because your game has amnesia. Today we fix that. First, let's understand how browsers can remember things:

How to Use LocalStorage in JavaScript — dcode

A clear walkthrough of the localStorage API — the exact tool you'll use to save and load game data. Pay attention to setItem, getItem, and JSON.stringify/JSON.parse.

Discussion after watching: Think about this: if your game saves EVERYTHING (including unbanked XP), does that break the banking mechanic? What should your game remember, and what should it forget on purpose?

Without persistence, closing the browser erases everything. We use localStorage to save and load the game state. But not everything should persist — some things are intentionally reset. What you choose to save is a design decision, not just a technical one.

You need: A web browser (Chrome recommended). Open any webpage and press F12 (or right-click and choose "Inspect"), then click the Console tab.

Step 1 — Store your name:

Type this in the console and press Enter:

localStorage.setItem("playerName", "Ami");

Now read it back:

localStorage.getItem("playerName");

You should see "Ami" appear. Your browser just remembered something.

Step 2 — Survive a close:

  1. Close the entire browser tab (not just the console — the whole tab).
  2. Reopen the same website. Open the console again.
  3. Type: localStorage.getItem("playerName");
  4. It's still there. Mind = blown.

Step 3 — Store a number:

localStorage.setItem("bankedXP", "250"); // Read it back localStorage.getItem("bankedXP"); // Note: it comes back as a STRING, not a number! typeof localStorage.getItem("bankedXP"); // "string"

Step 4 — Store an object (the game state trick):

const gameState = { bankedXP: 250, karma: 15, debt: 0 }; localStorage.setItem("gameState", JSON.stringify(gameState)); // Read it back const loaded = JSON.parse(localStorage.getItem("gameState")); console.log(loaded.bankedXP); // 250 — it's a real number again!

Step 5 — Clean up:

localStorage.removeItem("playerName"); localStorage.removeItem("bankedXP"); localStorage.removeItem("gameState"); // Everything is gone. Fresh start.

This is the exact API you'll use in your game. The setItem / getItem / JSON.stringify / JSON.parse pattern you just practiced is the same code in the scaffolding below. The only design question is: what do you choose to save?

Ami — Combat Risk Engine

What persists: Banked XP.

What does NOT persist: Unbanked XP. It is "at risk" — closing the browser is like dying. This reinforces the banking mechanic.

Ida — Economic Growth Engine

What persists: Karma and debt.

What does NOT persist: Active jobs. You have to accept new jobs each session. This means every play session starts with a fresh decision.

Save game state to localStorage:

function saveGame() { // Only save what should persist const saveData = { bankedXP: gameState.bankedXP, karma: gameState.karma, debt: gameState.debt // Do NOT save: unbankedXP, activeEvent, riskLevel }; localStorage.setItem("gameState", JSON.stringify(saveData)); console.log("Game saved!", saveData); }

Load game state on page start:

function loadGame() { const saved = localStorage.getItem("gameState"); if (saved) { const saveData = JSON.parse(saved); // Restore only persistent values gameState.bankedXP = saveData.bankedXP || 0; gameState.karma = saveData.karma || 0; gameState.debt = saveData.debt || 0; console.log("Game loaded!", saveData); } else { console.log("No save found. Starting fresh."); } // These always start fresh gameState.unbankedXP = 0; gameState.activeEvent = null; gameState.riskLevel = 1; gameState.zone = "safe"; }

Call saveGame() whenever state changes (after banking, after resolution). Call loadGame() on page load:

// On page load loadGame(); // After banking XP function bankXP() { // ... banking logic ... saveGame(); // Persist the banked XP }

Add a "New Game" button that clears everything:

function newGame() { localStorage.removeItem("gameState"); // Reset all gameState to defaults gameState.bankedXP = 0; gameState.unbankedXP = 0; gameState.karma = 0; gameState.debt = 0; gameState.riskLevel = 1; gameState.activeEvent = null; gameState.zone = "safe"; console.log("New game started. All progress cleared."); }
  • Complete the Console Magic activity (Steps 1-5)
  • Add saveGame() function that writes to localStorage
  • Add loadGame() function that reads on page load
  • Choose what persists and what doesn't — write it down
  • Test: refresh browser — banked progress remains
  • Test: verify unbanked XP / active jobs reset on refresh
  • Add a "New Game" button that clears localStorage
  • Checkpoint

    Refresh the browser. Banked progress remains. Unbanked progress is gone. This is intentional design.

    No cloud saves. Local only. No save slots. One save per game.

    Saving everything — including things that should reset. Be deliberate about what persists.

    Not parsing JSON on loadlocalStorage.getItem() returns a string. You must JSON.parse() it.

    Overwriting saved data with default state on load — make sure you load before initializing defaults, not after.

    Not handling the case where no save exists — the first time, getItem() returns null. Check for it.

    The Console Magic activity is the "wow" moment. When they close a tab, reopen it, and see their data is still there — that's when localStorage clicks. Do not skip it. Let them type every command themselves, not copy-paste.

    Ask: "Why should some things persist and others not?" This teaches that persistence is a design choice, not just a technical feature. The decision about what to save IS game design. Unbanked XP resetting on refresh reinforces the banking mechanic from Day 13 — it makes the browser itself part of the risk system.

    The JSON.stringify / JSON.parse pattern can be confusing. Explain it like this: "localStorage only speaks one language — strings. JSON.stringify translates your object into a string so localStorage can store it. JSON.parse translates it back when you load it."

    If a kid wants to save everything (including unbanked XP), push back: "What happens to the banking tension from yesterday? If unbanked XP survives a refresh, there's no risk to closing the browser. Is that the game you want?" Let them decide, but make them reason about it.

    Tomorrow is PLAYTEST DAY. No building, just playing and observing. Your game now has triggering, resolution, banking, and persistence — the full core loop. Tomorrow you'll play it silently for 5 minutes and discover what works, what breaks, and what's boring.