Onceworlds
Engine · Data

Worlds that last, and values every server shares

Worlds kept between sessions (Persist) and values every server shares.

The reference is "Kept worlds and shared data" in docs/engine/API.md; the SDK side is "Data that outlives a session" in the platform guide (onceworlds.com/agents.md).

Pick the place

You want Use Who changes it
A player's own progress (level, unlocks, settings) defineSave + Save(...) (onceworlds.data.player) that player's page
A server's world: a base, a farm, a town, a sandbox Persist(), or platform.data.server by hand the room's host only
One value for the whole game: a community goal, a wall, a record, today's event "data" in onceworlds.json + platform.data.game anyone, only by the declared operation, within its bounds

Server data lasts in private servers and named rooms. A friends game (Flow.dropIn({ friends: true }), or game.join({ private: true })) is the same private server each time a player starts it, so their world is there tomorrow, on any device. A public server's data ends with the server: Persist says so and keeps nothing.

Every page is untrusted. Server data trusts the host's page, as every host rule does: other players send the host requests (net.rpc, net.claim), and the host checks them before changing the world. Game data trusts nobody: the platform applies only the operation the key declares, within its bounds, a few times a minute per player. Declare the tightest bounds the game needs, and give out nothing valuable for game data.

Tycoon progress

A player's own factory that keeps growing while they're away. Machines are entities, cash is a resource: both kept.

import { createGame, defineComponent, defineResource, t, Transform } from '@onceworlds/engine';
import { Flow, Persist, persistOf, Sprite } from '@onceworlds/engine/modules';

const Machine = defineComponent('Machine', { kind: t.enum(['press', 'oven']), level: t.u8(1, { min: 1, max: 20 }) }, { save: true });
const Bank = defineResource('Bank', { cash: t.u32(), at: t.u32() }, { save: true });          // at: platform seconds

const game = createGame({
  modules: [
    Flow.dropIn({ friends: true }),                                         // each player's own private server
    Persist({
      rebuild: { Machine: (m) => [Sprite({ image: `assets/${m.kind}.png` })] },
      fresh: (world) => void world.spawn([Transform({ position: [0, 0, 0] }), Machine({ kind: 'press' })], { owner: 'host' }),
    }),
  ],
});
game.start();

// Away earnings: the platform's clock is the same for everyone (not the device's), so a page can't skip ahead.
persistOf(game)!.ready.then(() => {
  const bank = game.world.resource(Bank);
  const now = Math.floor(game.platform.now() / 1000);
  if (bank.at > 0) bank.cash += Math.floor(Math.min(now - bank.at, 8 * 3600) * incomePerSecond(game.world));
  bank.at = now;
});

Sandbox world

A shared build in a named room (or a friends server): players place blocks, the host checks and writes them, everyone sees them, and the next session finds them.

import { createGame, defineComponent, t, Transform } from '@onceworlds/engine';
import { Flow, Net, netOf, Persist } from '@onceworlds/engine/modules';

// Kept and sent: the next host can only save what reached its page.
const Block = defineComponent('Block', { kind: t.u8(0, { max: 15 }) }, { save: true, net: { replicate: true } });

const game = createGame({
  modules: [
    Net({ components: [Block] }),
    Flow.none({ join: { name: 'build-island' } }),                         // a named room: kept like a private server
    Persist({ key: 'island', rebuild: { Block: (b) => blockLook(b.kind) } }),
  ],
});
game.start();

const place = netOf(game)!.rpc('place', {
  args: { x: t.i32(0, { min: -64, max: 64 }), y: t.i32(0, { min: 0, max: 64 }), kind: t.u8(0, { max: 15 }) },
  to: 'host',
  rate: 8,
  validate: (_from, a) => !occupied(game.world, a.x, a.y) || 'taken',
  run: (_from, a) => void game.world.spawn([Transform({ position: [a.x, a.y, 0] }), Block({ kind: a.kind })], { owner: 'host' }),
});
place({ x: 3, y: 1, kind: 2 });

Survival base

A base that survives the night, written by hand with server data (no Persist): the host owns the truth, and saves once a night and when a wall changes.

const data = game.platform.data.server;

// Everyone: the base came with the room, no waiting.
const base = data.get('base') ?? { walls: [], chests: [], night: 1 };
buildBase(game.world, base);
data.on('change', (key, value) => key === 'base' && buildBase(game.world, value));

// The host: write when something changed, not every frame (true once the room has it).
async function keep(): Promise<void> {
  if (!data.canWrite) return;
  if (!(await data.set('base', baseOf(game.world)))) console.warn('base not kept yet: retrying at the next change');
}
game.room?.on('matchend', keep);

Game-wide meter

A goal every server fills together, starting over each day. Declare it in onceworlds.json:

"data": {
  "meter": { "type": "counter", "max": 1000, "add": 1, "reset": "daily" },
  "heroes": { "type": "list", "max": 10, "bytes": 64 }
}
const shared = game.platform.data.game;

// A player finished a delivery: one point, at most what "add" allows.
const total = await shared.add('meter', 1);
if (total === 1000) await shared.push('heroes', { name: me.name });

// Show it: read every few seconds, not every frame (reads are a few seconds old anyway).
setInterval(async () => {
  const now = await shared.get('meter');
  if (now !== null) meterBar.set(now / 1000);
}, 5000);