Repositories openvibers openvibe.games official

openvibers/openvibe.games

Games and creator worlds you play in the browser, with achievements on OpenVibe.Play.

Get it

Get this repository

Clone over HTTPS

A Git token is made for this copy and shown once. It can read and write the repositories you own, and expires in an hour.

git clone https://git.openvibe.codes/openvibers/openvibe.games.git
Clone to my computer

OpenVibe Link asks you on your computer. The folder is inside the root you chose in Link, and the request ends after two minutes.

Clone to my computer
Open in Codes

A Codes session clones from our disk: no network, no GitHub quota. When this repository has a GitHub twin, Codes pushes there too.

Open in Codes
Files at main
.env.exampleAI vs AI matches scored by Bench: Vibe Heroes drafts, replays, games.fun votes (off by default) (#199)15 days ago2.4 KB
.githubopenvibe-shared v4.223.0 (was v4.214.0) (#380)19 h ago
.gitignoreUntrack committed nodemodules symlinks and ignore them in git3 Oct 2026439 B
.gitleaks.tomlgitleaks: the made-up session token's allowlist also covers its first-commit path (sqlite/sqliteStore.test.ts), so main's secrets scan is green again (#99)27 days ago963 B
.npmrcM1 persistence: Games runs on PostgreSQL 18 + Valkey through openvibe-sdk (ADR-0007 decision 5). SQLite is gone (no data is restored; a new schema from scratch, no schema-version history). - packages/persistence: openPgStore on an openvibe-sdk/db handle, every repository async, batch upserts as multi-row INSERT ... ON CONFLICT in chunks of 500; migrations/0001_initial.sql: places, world_meta, world_entities and world_constraints keyed by place, characters (account_key is the subject, or guest: + sha256 of a guest token: a raw guest token is never stored), mods, grants, placements, audit, media mirrors, account data events, identity adoptions, and the SDK's event_outbox and token_revocations. transaction() hands the tx to its callback; the outbox enqueue takes it and refuses to run outside one. - The write-behind flusher (apps/server/src/game/flusher.ts): the tick builds a copy-on-write snapshot and never awaits I/O; one transaction at a time writes meta, NPCs (now dirty-tracked), entities, constraints, characters and their outbox events; a checkpoint while one is in flight is coalesced (flags stay set); a failed one restores its dirtiness. The shutdown checkpoint is never coalesced (queued behind one in flight), drain has a real deadline, a failed disconnect save retries in place with backoff, and a returning player's sign-in waits for queued saves before reading the character. - No database access on the tick: markets and env preload at boot, mod placements come from an in-memory mirror written through a serialized queue (rolled back if the write fails), an offline owner's friends load asynchronously (denied until loaded), hello resolves asynchronously with an in-flight guard. - Platform adapters on the SDK handle (createPgOutbox, createPgRevocationStore, account data), readiness via db.ready(), per-actor limits on Valkey when VALKEY_URL is set; development without DATABASE_URL runs on PGlite; migrations run as the owner on DATABASE_DIRECT_URL. Unhandled rejections closed in /api/v1/platform, readiness, the media mirror pass and account events; the Network revision advances only after its writes; a failed budget disable keeps enforcing. - openvibe-sdk v0.22.0, openvibe-shared v1.30.1; better-sqlite3 removed. Built by DeepSeek from Claude's spec; reviewed independently twice (FIX, FIX) and by Claude (the shutdown coalescing loss, drain deadline, disconnect retries and sign-in ordering fixed by Claude); every finding fixed with tests. pnpm typecheck, lint, test 872/872, slice test OK on PGlite, server bundle builds. Not deployed yet: production needs its database (roles/data/add-service.sh games) and Host's Games strategy moved off better-sqlite3 first.29 Sept 2026370 B
.prettierrc.jsonInitial OpenVibe release17 Aug 202690 B
AGENTS.mdAGENTS.md: the context an agent starts from (#379)1 days ago13.3 KB
ARCHITECTURE.mdGames M4: instances per place, fenced leases, room transfer, region interest (one process) (#45)12 Oct 20268.3 KB
CHANGELOG.mdCasual game view: a drag at its end scrolls the page (#384)16 h ago15.5 KB
CITATION.cffREADME first screen and CITATION.cff (#348)3 days ago499 B
CLAUDE.mdAGENTS.md: the context an agent starts from (#379)1 days ago11 B
README.mdopenvibe-shared v4.235.0 (was v4.234.0) (#397)14 min ago21.3 KB
ROADMAP.mdGames M4: instances per place, fenced leases, room transfer, region interest (one process) (#45)12 Oct 20268.6 KB
SECURITY.mdSECURITY.md: how to report a vulnerability ([email protected], 7-day reply, scope, supported versions)24 Sept 2026986 B
STATUS.jsonopenvibe-shared v4.235.0 (was v4.234.0) (#397)14 min ago3.6 KB
appsopenvibe-shared v4.235.0 (was v4.234.0) (#397)14 min ago
contentSource map names: surf_lagoon everywhere, old forms redirect (#195)15 days ago
deploynginx: the repo vhost is production's again (www redirect, security.txt, /tos) plus /internal/ and /metrics closed at the edge and HSTS, nosniff and a referrer policy (#98)27 days ago
docsRemove the last persistence helpers only account merge used (#376)1 days ago
eslint.config.jsInitial OpenVibe release17 Aug 2026637 B
package.jsonThe Source developer console: ~, cvars, commands, completions (#197)15 days ago1.3 KB
packagesopenvibe-sdk v0.161.0 (was v0.160.0) (#387)12 h ago
pnpm-lock.yamlopenvibe-shared v4.235.0 (was v4.234.0) (#397)14 min ago109.1 KB
pnpm-workspace.yamlM1 persistence: Games runs on PostgreSQL 18 + Valkey through openvibe-sdk (ADR-0007 decision 5). SQLite is gone (no data is restored; a new schema from scratch, no schema-version history). - packages/persistence: openPgStore on an openvibe-sdk/db handle, every repository async, batch upserts as multi-row INSERT ... ON CONFLICT in chunks of 500; migrations/0001_initial.sql: places, world_meta, world_entities and world_constraints keyed by place, characters (account_key is the subject, or guest: + sha256 of a guest token: a raw guest token is never stored), mods, grants, placements, audit, media mirrors, account data events, identity adoptions, and the SDK's event_outbox and token_revocations. transaction() hands the tx to its callback; the outbox enqueue takes it and refuses to run outside one. - The write-behind flusher (apps/server/src/game/flusher.ts): the tick builds a copy-on-write snapshot and never awaits I/O; one transaction at a time writes meta, NPCs (now dirty-tracked), entities, constraints, characters and their outbox events; a checkpoint while one is in flight is coalesced (flags stay set); a failed one restores its dirtiness. The shutdown checkpoint is never coalesced (queued behind one in flight), drain has a real deadline, a failed disconnect save retries in place with backoff, and a returning player's sign-in waits for queued saves before reading the character. - No database access on the tick: markets and env preload at boot, mod placements come from an in-memory mirror written through a serialized queue (rolled back if the write fails), an offline owner's friends load asynchronously (denied until loaded), hello resolves asynchronously with an in-flight guard. - Platform adapters on the SDK handle (createPgOutbox, createPgRevocationStore, account data), readiness via db.ready(), per-actor limits on Valkey when VALKEY_URL is set; development without DATABASE_URL runs on PGlite; migrations run as the owner on DATABASE_DIRECT_URL. Unhandled rejections closed in /api/v1/platform, readiness, the media mirror pass and account events; the Network revision advances only after its writes; a failed budget disable keeps enforcing. - openvibe-sdk v0.22.0, openvibe-shared v1.30.1; better-sqlite3 removed. Built by DeepSeek from Claude's spec; reviewed independently twice (FIX, FIX) and by Claude (the shutdown coalescing loss, drain deadline, disconnect retries and sign-in ordering fixed by Claude); every finding fixed with tests. pnpm typecheck, lint, test 872/872, slice test OK on PGlite, server bundle builds. Not deployed yet: production needs its database (roles/data/add-service.sh games) and Host's Games strategy moved off better-sqlite3 first.29 Sept 202672 B
testCasual game view: a drag at its end scrolls the page (#384)16 h ago
tsconfig.base.jsonInitial OpenVibe release17 Aug 2026568 B
tsconfig.solution.jsonGames: the casual line's kit and Vibe Heroes, an idle hero collector at /play/vibe-heroes (plan T22) (#58)30 days ago358 B
vitest.audit.config.tsInitial OpenVibe release17 Aug 2026251 B
vitest.config.tsM1 persistence: Games runs on PostgreSQL 18 + Valkey through openvibe-sdk (ADR-0007 decision 5). SQLite is gone (no data is restored; a new schema from scratch, no schema-version history). - packages/persistence: openPgStore on an openvibe-sdk/db handle, every repository async, batch upserts as multi-row INSERT ... ON CONFLICT in chunks of 500; migrations/0001_initial.sql: places, world_meta, world_entities and world_constraints keyed by place, characters (account_key is the subject, or guest: + sha256 of a guest token: a raw guest token is never stored), mods, grants, placements, audit, media mirrors, account data events, identity adoptions, and the SDK's event_outbox and token_revocations. transaction() hands the tx to its callback; the outbox enqueue takes it and refuses to run outside one. - The write-behind flusher (apps/server/src/game/flusher.ts): the tick builds a copy-on-write snapshot and never awaits I/O; one transaction at a time writes meta, NPCs (now dirty-tracked), entities, constraints, characters and their outbox events; a checkpoint while one is in flight is coalesced (flags stay set); a failed one restores its dirtiness. The shutdown checkpoint is never coalesced (queued behind one in flight), drain has a real deadline, a failed disconnect save retries in place with backoff, and a returning player's sign-in waits for queued saves before reading the character. - No database access on the tick: markets and env preload at boot, mod placements come from an in-memory mirror written through a serialized queue (rolled back if the write fails), an offline owner's friends load asynchronously (denied until loaded), hello resolves asynchronously with an in-flight guard. - Platform adapters on the SDK handle (createPgOutbox, createPgRevocationStore, account data), readiness via db.ready(), per-actor limits on Valkey when VALKEY_URL is set; development without DATABASE_URL runs on PGlite; migrations run as the owner on DATABASE_DIRECT_URL. Unhandled rejections closed in /api/v1/platform, readiness, the media mirror pass and account events; the Network revision advances only after its writes; a failed budget disable keeps enforcing. - openvibe-sdk v0.22.0, openvibe-shared v1.30.1; better-sqlite3 removed. Built by DeepSeek from Claude's spec; reviewed independently twice (FIX, FIX) and by Claude (the shutdown coalescing loss, drain deadline, disconnect retries and sign-in ordering fixed by Claude); every finding fixed with tests. pnpm typecheck, lint, test 872/872, slice test OK on PGlite, server bundle builds. Not deployed yet: production needs its database (roles/data/add-service.sh games) and Host's Games strategy moved off better-sqlite3 first.29 Sept 2026677 B

README.md

View file

!OpenVibe.Games

OpenVibe.Games

Browser games and creator worlds — openvibe.games. Play in the browser, with achievements on OpenVibe.Play.

Live site: https://openvibe.games · API docs: https://openvibe.services · For AI: https://openvibe.games/llms.txt
Run it: pnpm install && pnpm dev (Node 22 or newer) · Tests: npm test

Part of the OpenVibe network; help and the developer API.

Purpose

OpenVibe.Games hosts Scraplandia at https://openvibe.games and https://play.openvibe.games.
Scraplandia's extraction events double as its scored "Haul" mode: securing at-risk valuables at a
recovery bird earns haul points, with its own all-time/this-week leaderboard and achievements.
The same Games process also hosts Salvage Rush, an on-foot timed collection game with its own
place, world, rooms and round system. Play it at /play?game=salvage-rush on the same host.
See [docs/games.md](docs/games.md) for both games and local progression.
Server-run Vibe Heroes AI draft matches and Bench scoring are described in [docs/ai-matches.md](docs/ai-matches.md).
Players build their own creator worlds in the map editor and publish them at /worlds for anyone to join
(sandboxed: characters are not saved there); see [docs/games.md](docs/games.md#creator-worlds).
It also hosts the casual line (plan T22): small phone-first browser games built from one kit, starting with
Vibe Heroes, an idle hero collector at /play/vibe-heroes. See [docs/casual.md](docs/casual.md).
The player pages of OpenVibe.Play (the game services behind Games) live here too: /achievements, /leaderboards
and the API guide for game makers at /developers. They read Play's public API server-side; the service and its API
stay at play.openvibe.network, whose old pages 301 here.

A persistent multiplayer physics sandbox for the browser: Source-style movement,
physgun-driven building from crafted physical objects, gathering, crafting, and
extraction risk — running against an authoritative dedicated server.

Stack: TypeScript everywhere · Babylon.js (WebGPU with WebGL fallback) ·
Rapier physics (@dimforge/rapier3d-deterministic-compat, one pinned build
headless on the server and in client prediction) ·
WebSocket protocol · PostgreSQL 18 + Valkey persistence (openvibe-sdk) · pnpm monorepo.

Owns

  • the game: the authoritative simulation, the world, characters, inventories, skills, blueprints and
    props, in PostgreSQL (ADR-0007 decision 5: schema from scratch, migrations under
    packages/persistence/migrations, a write-behind flusher — the tick never awaits I/O)
  • the map editor and its assets, the mod registry (games-content@2 packs, grants, audit), the
    games-quickjs@1 server script mods (docs/mods.md) and the games.* events
  • the game WebSocket protocol and the portal, /play and /editor pages
  • the casual kit (packages/casual), its server economy (saves, ledger, idempotent actions) and the /play/<game>
    pages

Does not own

  • identity (OpenVibe.Network: players are keyed by Network subjects), file storage beyond the local
    copy (OpenVibe.Media mirrors map-editor assets), events delivery (OpenVibe.Events)
  • mod grants' authority (Network keeps them, mods.grant.manage) and general-purpose executable
    mods (OpenVibe.Host Stage C); Games runs its own bounded games-quickjs@1 scripts

Depends on

  • OpenVibe.Network (SSO, JWKS, client-credentials tokens, identity resolve, the games.progress.summary
    user module, mod grants), OpenVibe.Events (outbox relay, subscriptions), OpenVibe.Media (asset mirror)
  • openvibe-contracts v0.77.0, openvibe-sdk v0.22.0 and openvibe-shared v4.33.0 (pinned by release
    tarball in apps/server/package.json), Babylon.js, Rapier, PostgreSQL (pg, or embedded PGlite in
    development), Valkey (iovalkey, optional)

Repository layout

apps/
  client/        Browser client: rendering, prediction, input, HUD
  server/        Dedicated authoritative server: simulation, networking, persistence
packages/
  shared/        Math, ids, events, logging facade, fixed-timestep primitives
  protocol/      Versioned wire messages + codec (zod-validated inbound)
  content/       Data-driven item/recipe/world definitions + validation registry
  gameplay/      Pure domain logic: inventory, crafting, movement sim, zones, entities
  physics/       PhysicsWorld abstraction + Rapier adapter (@openvibe/physics/rapier)
  persistence/   DTOs, repository interfaces, PostgreSQL implementation, migrations
  casual/        The casual kit: rules, auto-battle and the Vibe Heroes pack (engine-free)
docs/adr/        Architecture decision records

See [ARCHITECTURE.md](ARCHITECTURE.md) for boundaries and data flow,
[ROADMAP.md](ROADMAP.md) for what exists and what is planned.

Prerequisites

  • Node.js >= 22
  • pnpm 10 (corepack enable)

Development

pnpm install

# Terminal 1 — authoritative server on :8000 (tsx watch)
pnpm dev:server

# Terminal 2 — client with hot reload on :5173 (proxies /ws to :8000)
pnpm dev:client

Open http://localhost:5173 in two browser windows to see multiplayer.

Controls: WASD move · Space jump (hold to bunnyhop) · Shift sprint ·
1-6 hotbar · Tab menu (inventory / crafting / skills / players) · G drop
held item · E gather / pick up props.
Physgun (GMod-style): hold LMB grab (grabbing a frozen prop unfreezes
it) · release to let go · RMB freeze · wheel push/pull · hold E rotate
like a globe (Shift+E snaps) · hold Shift for grid-lock.
Building: craft pieces → drop them (G or drag out of the inventory) →
position with the physgun → freeze. Dropping IS placement; E picks any
prop back up into your inventory.

The loop: spawn in Scrap City (safe city — no PvP, no building, no
physgun), gather branches/stones/scrap outside the gates, craft tools,
harvest the forest (axe/woodcutting) and quarry (pickaxe/mining), craft
building pieces, place them in the wilds, position with the physgun and
freeze. Props are protected: only you and players you trust can move them.

Commands

| Command | Purpose |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| pnpm dev | Server + client dev processes in parallel |
| pnpm test | All unit tests (vitest, through test/run.js) |
| pnpm typecheck | Strict TypeScript across every package |
| pnpm lint | ESLint (typescript-eslint, no-explicit-any) |
| pnpm format | Prettier write |
| pnpm build | Production build of all packages + client bundle |
| tsx apps/server/scripts/sliceTest.ts | End-to-end vertical-slice test (boots a real server, drives protocol clients, restarts, asserts persistence) |

Production

pnpm build
DATABASE_URL=postgres://… DATABASE_DIRECT_URL=postgres://… STATIC_DIR=apps/client/dist PORT=8000 pnpm --filter @openvibe/server start

DATABASE_URL (pooled, through PgBouncer) serves; migrations run at boot on DATABASE_DIRECT_URL
(the owner role, a direct session). With neither set and NODE_ENV not production, the server uses
an embedded PGlite database under DATABASE_DIR (default data/) — development only, one process.
GAMES_PLACE_ID (default scraplandia) selects the default place for older clients and the
edited map. Both built-in places have separate world rows and instance leases. VALKEY_URL
(optional) makes the per-actor limits and the WebSocket upgrade tickets shared across instances
(issue is SET … PX … NX, consume is GETDEL, so a ticket is single-use cluster-wide).
GAMES_INSTANCES_PER_PLACE (default 1) enables several room simulations for a place in this process;
MAX_PLAYERS is per-room capacity. PostgreSQL records each room and its renewable fenced lease.
GAMES_INTEREST_CELL (default 16) and GAMES_INTEREST_RADIUS (default 80) tune spatial replication.
See [docs/instances.md](docs/instances.md) for transfer, lease expiry and the one-process limit.
TRUST_PROXY (optional) says which proxies may set the client-address headers: unset means the TCP
peer is the client and X-Forwarded-For/X-Real-IP/CF-Connecting-IP are ignored (they are
client-supplied and would otherwise let a caller mint fresh rate-limit buckets); true means the
server is only reachable through the proxy; otherwise a comma-separated list of trusted proxy
IPs/CIDRs (behind nginx, which sets X-Real-IP from $remote_addr). See [.env.example](.env.example).

The server serves the built client, /healthz (liveness), /api/ready, /metrics, and the game
WebSocket on one port. Pages are / (portal), /play and /editor; on play.openvibe.games / is
the game and /play redirects there. Any other path that is neither a file of the build nor a route
answers 404 (an HTML page with links to those pages; {"error":"not_found"} under /api; plain text
for a missing asset), never the portal with a 200. /api/ready is 200 only while the database answers a real round trip
(db.ready()) and the simulation
has ticked within the last 5 s, otherwise 503 naming the failed check (status
ready/not_ready, checks.db, checks.tick); nginx keeps it off the public vhosts. Environment: PORT, HOST, STATIC_DIR,
MAX_PLAYERS, LOG_LEVEL, GAMES_PLACE_ID, the database variables above (platform variables below).

Deployed at https://openvibe.games (nginx TLS termination → server on 127.0.0.1:8000,
systemd unit openvibe-games.service from [deploy/openvibe-games.service](deploy/openvibe-games.service),
env file /etc/openvibe/games.env (which holds DATABASE_URL, DATABASE_DIRECT_URL and VALKEY_URL);
play.openvibe.games Host-routes to the same process).

Deploy on the host with sudo /opt/openvibe.games/deploy/scripts/deploy.sh, which runs
ovhost deploy games (OpenVibe.Host, strategy pnpm-build; roadmap WS-N task 11): a fast-forward pull as
the checkout owner, pnpm install --frozen-lockfile, every workspace package's dependencies checked, pnpm build,
the restart (the SDK migrator applies any new migration as the owner before serving), /api/ready, and on failure the checkout
restored, reinstalled and rebuilt and the server restarted again. Players online are reported and
reconnect; --wait-idle holds the restart until nobody plays, --rollback runs ovhost rollback games,
DRY_RUN=1 prints ovhost plan games. Do not pull by hand first (ovhost would find nothing new; pass
--restart if you did). The wrapper exits with an error if ovhost is missing.

Rollback: sudo deploy/scripts/deploy.sh --rollback (ovhost rollback games), which rebuilds the previous
commit. Migrations are additive and applied at boot, so an older build starts on the newer schema; the SDK
migrator refuses a migration file that was edited after it ran (write a new one instead).

Platform integration (OpenVibe network)

Games integrates with the rest of OpenVibe at its service boundary only
(apps/server/src/platform, apps/server/src/mods); the simulation packages
do not know the platform exists. See [ADR-0006](docs/adr/0006-canonical-subjects-and-platform-boundary.md).

  • Identity. Signed-in players are keyed by their canonical
    openvibe.network subject (usr_…/gst_…, the token's subject_id); a
    token without one cannot name an account. Guest tokens that look like
    account keys (usr_…) are refused.
  • Service principal. Calls to Events, Media and Network identity carry a
    client-credentials token of the games OAuth client (same client id and
    secret as SSO), one per audience. There are no shared keys. The SSO calls
    made for a player (/api/auth/me, code and FedCM exchanges) keep using the
    player's own token.
  • Events (OpenVibe.Events, through a transactional outbox in
    PostgreSQL, table event_outbox): games.player.joined|left,
    games.skill.leveled, games.blueprint.unlocked, games.world.saved
    (at most one checkpoint per WORLD_SAVED_EVENT_MINUTES, plus shutdown) and
    games.mod.installed|enabled|disabled|grants_changed|revoked. Achievements are
    not published as events: an unlock is stored in Games (its achievement rules read
    those tables) and sent to OpenVibe.Play over Play's API (GAMES_PLAY_URL), which
    is the network-wide read surface for titles, scores and unlocks.
    Progression events come from what a save actually wrote, in the same
    transaction.
  • Inventory. Six built-in achievements give Live items: Scraplandia First Haul (Toxic Name), Big Haul (Golden Name), Haul Week (Cowboy Hat); Salvage Rush First Haul (Neon Name), Big Haul (Flames), Arena Winner (Stars). Games grants them after unlock or guest adoption using its own OAuth client; failed grants retry. Inventory items are free, never sold or traded.
  • Media. Map-editor assets (textures, paint masks, glb models; the only
    uploaded files Games has) are still served from local disk and are also
    copied into Media objects (kind: asset, namespace MEDIA_NAMESPACE), with
    a restart-safe queue (media_mirrors). There are no screenshots or blueprint
    files to upload: blueprints are per-player recipe unlocks.
  • Mods (ADR-013 in OpenVibe.Contracts). Manifests follow
    [email protected] (Games validates against the schema in the
    installed openvibe-contracts package — pin drift is a defect, ADR-0007
    decision 12). The content runtime is games-content@2:
    declarative data packs checked against @openvibe/content (announcements;
    inert, mod-owned props; definitions — items, recipes, crops, NPC archetypes —
    merged into the content registry at boot, so they take effect on the next
    restart). The client sends its definition digest in hello; the server checks it before joining and echoes its digest in welcome. A mismatch is rejected so installed definitions cannot silently desync clients. Scraplandia's default blank-slate map is supplied by the built-in pack. Each install stores the approved subset of its
    requested capabilities; every runtime binding checks it at call time, a
    revoked install or capability stops affecting the world on the next tick,
    and install/grant/use/deny/revoke are audited (mod_audit). Trust tiers are
    metadata only. Staff can install games-quickjs@1 script packs through the mods API;
    built-in packs can carry scripts too. Scripts are pinned by the def-set digest and run
    in a per-mod QuickJS sandbox only where GAMES_SCRIPT_MODS=1 (budgets
    cpuMs 2 per tick, memoryMb 8, stackKb 128 by default; over budget
    disables the mod for the instance; host API and limits in docs/mods.md). API: GET /api/v1/mods,
    GET /api/v1/mods/:id (public); POST /api/v1/mods,
    POST /api/v1/mods/:id/enable|disable|revoke|grants|reload,
    DELETE /api/v1/mods/:id/grants/:capability, GET /api/v1/mods/:id/audit
    (owner/admin session, or a principal token with games.mod.manage). Fresh clients fetch
    GET /api/v1/content before the digest handshake so live script changes can be joined.
  • Status: GET /api/v1/platform reports whether events and the Media
    mirror are on, the outbox backlog and mirror counts (no secrets).
  • In production (2026-09-23, release f11e21c): events and the Media
    mirror are on. games.world.saved events reach OpenVibe.Events; no mod is
    installed, no grant exists and no asset has been mirrored to Media yet.
  • OpenVibe Frame. The portal, /play and /editor load the OpenVibe Frame
    (navbar, footer, theme loader) from this server's own pinned copy at /shared/
    (apps/server/src/net/sharedAssets.ts), so they keep their frame while Network is down.
  • Persistence proof. apps/server/src/game/platformIntegration.test.ts
    boots the real server twice on one database and checks characters, world
    props, the world clock and the mod registry survive the restart;
    sliceTest.ts covers the gameplay state end to end.

Platform environment (all optional; service calls need OV_OAUTH_CLIENT_SECRET):

| Variable | Purpose |
| ---------------------------------- | ------------------------------------------------------------------------------------------ |
| OV_OAUTH_CLIENT_ID | OAuth client / principal id (default games) |
| OV_OAUTH_CLIENT_SECRET | Its secret: enables SSO and every service call |
| OV_NETWORK_INTERNAL_URL | Network base for /oauth/token, identity and the JWKS (e.g. http://127.0.0.1:4000) |
| EVENTS_URL | OpenVibe.Events base; enables the outbox (EVENTS_PUBLISH=off disables) |
| MEDIA_URL | OpenVibe.Media base; enables the asset mirror (MEDIA_MIRROR=off disables) |
| GAMES_INVENTORY_URL | Inventory base (default https://inventory.openvibe.network; production can use loopback) |
| GAMES_ITEMS | on (default) or off; off also when the OAuth client secret is unset |
| GAMES_PLAY_URL | OpenVibe.Play base (e.g. http://127.0.0.1:5040); unset = scores and unlocks stay local |
| MEDIA_NAMESPACE | Media tenant for the copies (default games) |
| WORLD_SAVED_EVENT_MINUTES | Checkpoint event interval (default 15) |
| GAMES_CREATOR_WORLDS_MAX | Creator worlds running at once (default 2; 0 stops new ones starting) |
| GAMES_CREATOR_WORLDS_PER_PERSON | Worlds one account keeps (default 5) |
| GAMES_CREATOR_WORLD_IDLE_SECONDS | An empty creator world stops after this long (default 180) |
| GAMES_CREATOR_WORLD_PLAYERS | Players per creator world (default 16) |
| GAMES_CREATOR_SCRIPTS | 1 runs creator worlds' own scripts in their rooms (QuickJS sandbox; off by default) |

Capabilities

Implemented here (the service manifest's capabilities): games.mod.manage and games.mod.read (the
mods API, for a principal token or an owner/admin session); games.world.announce,
games.prop.place, and games.def.define for declarative packs; and
games.script.run, games.players.read, games.player.message,
games.world.emit, and games.storage.kv for script packs. Script bindings
are absent unless approved for that install.

Service grants the games principal needs:
inventory.item.grant (openvibe.inventory), events.event.publish and events.subscription.manage (openvibe.events: the outbox, and the account,
revocation and mod-grant subscriptions), media.object.upload
(openvibe.media, namespace games), identity.subject.resolve
(openvibe.network), network.modules.write on games.progress.summary, and mods.grant.manage
(Network's mod grants, roadmap WS-M task 3).

Tests

pnpm test runs every vitest file (unit tests of the packages and the server, including
apps/server/src/game/platformIntegration.test.ts, which boots the real server twice on one database);
it goes through test/run.js, which maps the flat test/*.test.js pointers back to the colocated
TypeScript tests so ov test can select and shard them (vitest reads the real files, so their
@vitest-environment docblocks and imports are untouched). After adding or moving a *.test.ts,
run node test/run.js --index to regenerate the pointers — a missing or stale pointer fails the run.
pnpm typecheck and pnpm lint keep strict TypeScript and no any;
tsx apps/server/scripts/sliceTest.ts drives protocol clients against a real server across a restart.

Security

Reporting a vulnerability: [SECURITY.md](SECURITY.md). The server is authoritative: inbound protocol
messages are validated (zod), the WebSocket has per-socket flood control, and HTTP writes that cross a
capability boundary (editor saves and uploads, the mods API) have per-actor limits (openvibe-sdk/limits).
Players are keyed by Network subjects; guest tokens that look like account keys are refused.
Script mods run in isolated QuickJS sandboxes with only their approved bindings.
nginx keeps /api/ready off the public vhosts and /metrics answers direct loopback callers only. OV_OAUTH_CLIENT_SECRET, GAMES_EVENTS_SECRET (signed
Events deliveries) and OV_NETWORK_AUTH_URL live in /etc/openvibe/games.env, by name only; there are no shared keys
between services.

<!-- versions:start -->

  • openvibe-contracts: v0.422.0
  • openvibe-sdk: v0.161.0
  • openvibe-shared: v4.235.0
  • openvibe-publishing: v1.7.0

<!-- versions:end -->