The tap-to-command prototype had a hole in the middle of it: you could spend an hour digging a shaft, sealing a room, wiring a generator and getting a planter running — and a reload threw all of it away. That is now fixed. The base saves itself, and it keeps running while you are away.
What gets saved
One key in localStorage, about 27 KB. The world is a single byte array of 70,560 tiles, so it is stored run-length encoded in base 36 — a chewed-up world compresses to roughly 6,500 runs. Alongside it: the hold, produce and rations, fitted upgrades, per-generator fuel, per-planter growth, per-galley cooking progress, the weather, the day clock, which landmarks you have found, the camera, and each crew member’s position, warmth, food, state and task queue — including the queue that is held during a cold retreat, so a suit halfway to shelter resumes properly.
Two things are deliberately not saved: build ghosts and material reservations. Both are derived from the crew’s queues, so they are rebuilt by replaying those queues on load. Storing them would have made it possible for a ghost and its reserved materials to disagree; deriving them makes that impossible by construction.
A corrupt save has to be boring
The failure cases matter more than the happy path, because the happy path is the one you notice working. Tiles decode into a scratch array first, so a save truncated mid-write can never leave a half-written world. Bad JSON, an older save version, a world signature that no longer matches the current worldgen or build palette, a short run-length stream — all of them clear the key and start a fresh world. Nothing throws. Every write is wrapped, so a full or blocked storage quota warns once and then stops trying rather than complaining every fifteen seconds.
Autosave runs on a 15-second timer, and also on visibilitychange and pagehide, which is what actually catches a phone being backgrounded mid-dig.
The Reset button that wouldn’t reset
My favourite bug of the day. ↻ Reset arms on the first tap and wipes on the second, then reloads the page. It didn’t work: every wiped base came straight back. The reload fires pagehide, the pagehide handler saves, and the save it wrote was the base that had just been deleted. Reset now suspends saving before it wipes.
The other thing persistence forced was the clock. The frame loop used the raw animation timestamp as its clock, which resets to zero on reload — so every saved cooldown (meal timers, warning throttles, the day cycle) would have been in the impossible future. The clock is now accumulated from frame deltas and saved with everything else.
Four hours of being away
On load, the gap since the last save is clamped to four hours and simulated: generators burn sulfur, planters grow, the galley cooks. Vitals are frozen — nobody starves or freezes in a room with nobody watching, which keeps the no-death-state rule intact. Offline burn runs at 0.35×, because at full rate four hours away drains an entire hold of sulfur and returning is a punishment rather than a reward. The loading banner tells you what happened while you were gone, including the bad news:
"Away 2h 0m — 31 grown · 17 cooked · 40 sulfur burned · generator ran dry."
Verified
Headless in Chromium and then live on both a phone and a desktop viewport: run-length round-trip lossless over a dug-out world; tiles, hold, upgrades, stores, generator fuel, crew vitals and queues all restored; ghosts and reservations correctly rebuilt; truncated and stale-version saves both fall back to a fresh world; autosave fires unattended; Reset produces a virgin world; two hours and forty hours away (clamped to four) both report correctly. Zero page errors.
One unrelated fix fell out of it: discovering a landmark was marking the survey map clean instead of dirty, so the minimap never refreshed after a find.
Live: orbit2d.co. If you had a base there before today, it will be gone — there was nothing to migrate from.
Leave a Reply