VicroCode
Make Code Create Value
VicroCode is a lightweight online platform for publishing, running, sharing, and monetizing code projects. Launch HTML, Python, SQLite, AI agents, management tools, games, and more without server setup.
Please wait while VicroCode loads. You can also explore the AI programming guide
Loading...

AI MARKET GUIDE

The durable half of an idle game: server-authoritative offline progression on hosted HTML, Python, and SQLite

A dev revived a dead idle web game from web archives. The reusable move isn't the AI art. It's computing offline gains server-side from stored timestamps.

Someone in the community rebuilt a placement/idle web game called 《修真》 that had been gone for years. The original shipped in 2008 from a Guangzhou publisher and shut down in 2011, and no source ever leaked. The dev pointed AI at whatever was left online: fragment screenshots, historical web-archive snapshots, old news write-ups, forum threads, and turned that into a playable remake, with the screenshots processed into reusable game assets since there were no original client files at all. They put their own reconstruction fidelity at roughly 80%, which is their claim, not a verified measurement.

The part everyone latches onto is the asset side: AI redrawing sprites from grainy captures, the "revive a dead thing from collective memory" framing. It's a good story. But it's not the reusable engineering lesson. If you're building any idle or placement game, the part that actually decides whether your remake survives contact with real players is much less romantic: how you handle progression while the player is away.

Why the browser clock will betray you

Idle games live or die on offline progression. You close the tab, come back tomorrow, and the game hands you the resources you "earned" while gone. The naive way to build that is to read the client's clock, subtract the last-seen timestamp stored in the browser, and multiply by the per-second rate. It works perfectly in your own testing and falls apart the moment a stranger plays it.

The failure mode is obvious once you say it out loud. If the browser owns the clock, the player owns the clock. Set the system time forward a year, reload, collect a year of gains. Edit the number in local storage. Replay the request. There's no accusation of bad faith here, it's just that any value the client computes is a value the client can fake, and idle games are unusually easy to cheat because the entire loop is "time in, currency out."

So the durable half of the build is a boundary decision: the client shows progression, the server decides progression. The browser can animate a counter ticking up so the game feels alive, but the authoritative balance only ever changes when the server recomputes it from timestamps it trusts.

The shape of the build

You can put the whole thing together with three pieces that map cleanly onto what's available.

The front end is a plain HTML/JS game view. It renders the current state, animates the idle tick locally for feel, and calls the backend on load, on meaningful actions, and on a periodic sync. Nothing here needs to be trusted. You can host and serve that front end directly when you run HTML online, which keeps the player-facing surface and the deployment in one place instead of stitching together a separate static host.

The brain is a Python endpoint that computes elapsed-time progression deterministically. "Deterministically" is the word that matters. Given the same stored state and the same server-side `now`, the same inputs always produce the same output, no randomness that the client can reroll, no dependence on how often the client happened to call in. When you run Python online as an API endpoint, the request flow looks like this:

import time

def compute_progress(last_tick_ts, rate_per_sec, resources, now=None):
    now = now if now is not None else time.time()
    # Guard against clock skew or a replayed/forged earlier timestamp.
    elapsed = max(0.0, now - last_tick_ts)
    # Optional: cap offline accrual so a month away isn't a jackpot.
    elapsed = min(elapsed, 7 * 24 * 3600)
    gained = elapsed * rate_per_sec
    return {
        "resources": resources + gained,
        "last_tick_ts": now,
    }

Two small guards do most of the anti-cheat work. `max(0.0, ...)` means a forged earlier timestamp can never mint negative time into a bonus, and the cap on `elapsed` means being away for a month doesn't hand out a month of runaway currency. The server reads `last_tick_ts` from its own store, never from the request body, and stamps the new `last_tick_ts` with its own clock. The client's clock is never in the equation.

The memory is a per-player save in SQLite. One row per player is enough to start: player id, resource balances, upgrade levels, and the server-owned `last_tick_ts`. Every sync is read-modify-write inside the endpoint. Keeping saves in a real relational store rather than a JSON blob pays off the first time you need to debug why one player's balance looks wrong, because you can open the SQLite editor and inspect or correct a single row directly instead of decoding an opaque save string.

A minimal schema:

CREATE TABLE IF NOT EXISTS players (
    player_id     TEXT PRIMARY KEY,
    resources     REAL NOT NULL DEFAULT 0,
    rate_per_sec  REAL NOT NULL DEFAULT 1,
    last_tick_ts  REAL NOT NULL
);

Trade-offs I'd flag before you commit

Server-authoritative doesn't mean chatty. If you recompute on every animation frame you've built a load problem for no gain. Recompute on load, on purchases and other state-changing actions, and on a slow heartbeat, and let the client fake the smooth ticking in between. The client's displayed number and the server's truth will drift by a few seconds, which is fine, they reconcile on the next real call.

Watch the identity question early. "Per-player save" assumes you know who the player is. Anonymous device ids are easy and let people play instantly, but they also mean a cleared browser loses the save and there's no real account boundary. That's a genuine security consideration rather than a detail: without some authenticated player identity, one client can claim to be another simply by sending a different id, so decide up front whether anonymous ids are acceptable for your game or whether you need real accounts, and don't quietly ship the unauthenticated version as if it were the finished one.

Determinism is a discipline, not a one-liner. The moment you add offline combat, random drops, or events, you have to decide those outcomes server-side too, or seed them from server-owned state, otherwise you've reopened the exact hole you closed on the resource counter.

Where the boundary actually sits

Be honest about what this stack does and doesn't cover. It gives you a hosted HTML client, a Python progression brain, and durable per-player SQLite saves, which is the entire durable half of an idle game. It does not, on its own, give you real-time multiplayer sync, a native mobile client, or a third-party auth provider, and I'm not going to pretend otherwise. Those are separate problems that sit outside this set of pieces, and if your remake needs them you're scoping a bigger project.

The reason the 《修真》 revival is worth learning from isn't the nostalgia or the AI redraw. It's that the genre's real fragility was never the art. Art you can reconstruct from screenshots. Trust you can't reconstruct after players have found the clock exploit and drained the fun out of your economy. Build the server-authoritative half first, keep the timestamp logic deterministic and on the server, and the charming front end you spent all that effort reviving actually gets to stay alive.