Skip to content

Permissions & Quotas ​

The containment contract: what you must declare, what you get, and what happens when limits are hit. None of it is optional - quotas are always on, for every mod, in every world.

Permissions ​

Declared in the manifest, shown to server owners at install time, and hard-enforced on every applicable runtime side. Server denials log denied: requires the '<permission>' permission; client hooks return inert/empty handles and warn once.

PermissionGatesEnforcement
world.entitiesctx.entities.*, client entity hookshard (both)
world.entities.readbounded ctx.world.entities.get/query/findNearby and useWorldEntity/Query projectionshard (both); player projections additionally require players.read
world.zonesbounded ctx.zones.* and authoritative onZoneEnter/onZoneExit factshard (server)
world.interactionsdeclarative ctx.interactions.*, client interaction hooks and authoritative completionhard (both)
world.activitiesbounded ctx.activities.* graph instances, managed persistence and onActivityEventhard (server)
storagectx.storage.*hard (server)
players.modifyctx.rewards.grantMoney/grantXp/spendMoneyhard (server)
players.readplayer snapshot APIs and join/leave lifecyclehard (both); unauthorized worker snapshots are empty
world.objects.readctx.objects.list/get (server), useWorldObjects (client)hard (both)
world.objects.writectx.objects.setFarm/setProperties on owned presetshard (server)
players.inventoryctx.inventory.count/grant/removehard (server)
players.teleportctx.players.teleporthard (server)
players.damagectx.players.applyDamage (bounded, host-clamped)hard (server)
audiouseModAudioBuffer, useModAudioBus, useModSpatialAudio and host audio accesshard (client SDK returns disabled/empty handles)
ui.hudHudPanel, semantic input actions, notifications, host HUD surfaces and dialogshard (client; undeclared HUD is not mounted)
camera.controlbounded useModCameraShotRegistration presentation leaseshard (client; cannot override core camera modes or transfer camera ownership)
physics.colliderscurated static/kinematic/client-local dynamic colliders, server-authoritative dynamic entity bodies/actions, bounded presentation-control leases, and closest-hit raycast via @vibelands/mod-sdk/physicshard (client capability and server dynamic-body/lease access both reject access without permission; a lease never transfers server authority)
physics.jointsbounded ctx.physics.createJoint/removeJoint/getJoint/listJoints and onPhysicsJointEvent for mod-owned authoritative bodieshard (server); does not grant body creation or access to another mod's entities

Unknown permission ids fail manifest validation in check and at server discovery.

Quotas & budgets ​

ResourceLimitOver-limit behavior
Replicated entities384 per mod per worldspawn() returns null, logged
Entity properties JSON≤ 2 KB per entityspawn/update rejected
Entity component descriptors / values32 descriptors per mod, 16 component values per entity; shares the 2 KB entity data budgetunknown, invalid or oversized component data rejected
World entity queries10/s, burst 20, ≤ 256 results, ≤ 2,048 m radiusempty result after rate exhaustion; inputs are clamped
Zones / interactions64 zones; 128 interactions, ≤ 20 m range, ≤ 10 s holdinvalid or over-quota registration returns null; invalid intents never reach mod code
Activities32 definitions, 128 nodes/definition, 64 instances, 16 participants, 16 KB variables/instanceinvalid graphs or over-quota operations return false/null
Physics joints/actions128 joints/mod; 120 body/joint mutations/s, burst 240invalid/foreign endpoints return null; exhausted action budget returns false/null; endpoint and mod cleanup destroy joints
Entity transform updates10 Hz per entity, host-batchedfaster updates merge into a pending patch - final state always lands
Player damage≤ 50 HP per applyDamage call; 4 calls/s per mod (burst 8); dead players rejectedover-limit call returns false, logged
Replicated KV blob≤ 8 KB, ≤ 2 writes/s (burst 4)over-rate writes defer, last write wins
Persistent storage≤ 32 KB per value, ≤ 2 MB per mod per worldset() returns false, logged
Inbound messages20/s per mod per client (burst 40), ≤ 8 KBsilently dropped
Server tick budget100 ms synchronous hook time per 5 s windowstrike; 3 consecutive strikes → disabled for that room
Hook errors5 per 5 s windowdisabled for that room
Client frame budgetadvisory ~1.5 ms avgvisible in the debug panel; egregious cases get flagged
Client bundle≤ 1.5 MB gziplocal check and the hosted build fail
Assets≤ 25 MB per modlocal check and the hosted build fail

Awaited I/O (ctx.storage, DB latency) never counts against the tick budget - only CPU you actually burn.

The circuit breaker ​

When a mod trips the budget or error threshold:

  1. it's disabled for that room only - other worlds running it are unaffected
  2. its timers are cleared, its replicated entities and KV state removed
  3. a loud DISABLED line lands in the server log
  4. disabledReason appears in GET /admin/mods health and the in-game store

Re-enabling via the store/admin API is the owner's explicit "try again" - it restarts the mod fresh.

Client-side containment ​

  • Every surface mounts inside its own React error boundary: a render crash unmounts that mod only and flags it in window.__VIBELANDS_MODS__.status().
  • useModFrame swallows and counts callback errors; after 50 the callback is disabled.
  • Load failures (bad integrity, missing bundle, no surfaces) skip the mod with a visible warning - never a white screen.

Observability ​

js
// Browser console
window.__VIBELANDS_MODS__.status()        // { [id]: { status, error? } }
window.__VIBELANDS_MODS__.frameMetrics()  // { [id]: { avgMs, maxMs, samples } }
bash
# Admin API - per-world live health
curl -H "x-admin-key: $KEY" http://localhost:2567/admin/worlds/<id>/mods
# → health: [{ modId, enabled, disabledReason, entityCount, windowExecMs, overBudgetStrikes }]

Visual panel: bottom-left of the screen at debugMode ≥ 1 - status, [installed] source marker, avg/max frame ms per mod.

VibeLands Creator · Runtime API v2