players
The curated player surface. Wraps the raw `UserIdentity` component proxy in a view that exposes only the safe authoring API: `.isLocal` / `.avatar` / `.ready` / `.userId` / `.identity` / `.displayName`.
modules.api.engine.players
The curated player surface. Wraps the raw UserIdentity component proxy
in a view that exposes only the safe authoring API:
.isLocal / .avatar / .ready / .userId / .identity /
.displayName.
.avatar is the visible body's entity REF (a live proxy), or nil until a
body is bound — act on it directly (player.avatar.position = { x, y, z }).
Want the id? player.avatar.id. Assign a live entity ref to bind it as
the body — player.avatar = entityRef (assign nil to clear) is the one
path that binds a body to the player.
.entity / .entityId / .id / .component are intentionally REFUSED.
The identity entity is a internal anchor with no world presence — reach the
visible body via player.avatar. Position writes always target the avatar.
Used by every layers.active.players.localPlayer / onLocalReady /
playerJoined / playerLeft call site so user scripts can't reach
past the curated wrapper into the underlying component proxy.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2module players Per-scene players registry. Tied to a non-additive Scene proxy. Reached as `layers.active.players` or `layers.find(name).players`. Returns curated player handles — never entity or component proxies — so the caller reads/writes player data (`.userId`, `.displayName`) and reaches the body via `player.avatar` (an entity ref) uniformly across all consumers. Both colon (`p:onJoin(cb)`) and dot (`p.onJoin(cb)`) call styles are supported on every method; the registry is a namespace surface, not an object, so neither style is canonical. Additive layers do NOT carry a players surface today (multiplayer support for players living inside additive overlays is a follow-up — see the layers.module additive-scene comment). `layers.find(<additive>).players`
localPlayerEntityId( ) → void
Resolve THIS client's local identity entity id from the `player_entity_id` world arg, which the engine publishes as soon as the local identity is spawned — available in the bootstrap window before the UserIdentity component has attached. Identities are internal (no 3D world presence) and self-register into the scene registry, so this reads the engine-published arg and never searches the world; it returns nil until the arg lands. This resolver is ROOM-scoped ("which identity entity is mine in this session") and deliberately does NOT consult `world.connectedUsers` — that is the WORLD-level identity surface, and its `user.entity` resolves an avatar by walking BACK through this registry, so reading it here would be a cycle. This chain is the canonical answer to "what's MY identity entity?" across every caller — never duplicate it in caller code.
resolvePlayerComponent(eid: ?) → void
| arg | type | description |
|---|---|---|
| eid | ? |
avatarRef(avatarId: ?) → void
Resolve an avatar entity-id string to its entity REF (proxy), or nil when no avatar is bound or the entity isn't live. `player.avatar` hands back this ref — never a bare id — so callers act on the body directly (`player.avatar.position = ...`) the way every other entity-returning API does (refs, not strings).
| arg | type | description |
|---|---|---|
| avatarId | ? |
liveIdentityEntities( ) → void
entitiesRevision( ) → number
The engine's monotonic entity-snapshot counter, bumped on every spawn, despawn, and component change. Reading it costs a fraction of a microsecond against several for an enumeration, so it gates the reconcile below down to one integer compare while the world holds still. nil when the counter is unavailable, which makes the reconcile run on each read.
wrapPlayer(comp: ?) → void
| arg | type | description |
|---|---|---|
| comp | ? |
isLocal( ) → void
__index(_: ?, k: ?) → void
| arg | type | description |
|---|---|---|
| _ | ? | |
| k | ? |
__newindex(_: ?, k: ?, v: ?) → void
| arg | type | description |
|---|---|---|
| _ | ? | |
| k | ? | |
| v | ? |
new(sceneProxy: ?) → void
Per-instance state and callback registries. One block per non-additive Scene proxy, built by `Players.new(sceneProxy)` and stored on the proxy by layers.module.
| arg | type | description |
|---|---|---|
| sceneProxy | ? |
pushCb(reg: ?, fn: ?) → void
| arg | type | description |
|---|---|---|
| reg | ? | |
| fn | ? |
removeCb(reg: ?, h: ?) → void
| arg | type | description |
|---|---|---|
| reg | ? | |
| h | ? |
fireCb(reg: ?, ...: ?) → void
Fire every subscriber under pcall so one buggy listener can't block the rest. Errors land in the log as warnings, keyed by subscriber handle for traceability.
| arg | type | description |
|---|---|---|
| reg | ? | |
| ... | ? |
listRegistered( ) → void
Snapshot the registered players (this room's connected players) into an array of curated player handles. Reads the IDENTITY REGISTRY, never a scene query — the identity entity is internal and unfindable by design. Stale entries (entity gone without a clean `playerLeft`, e.g. an abrupt relay despawn) are skipped AND pruned so the registry self-heals.
list( ) → void
get(key: ?) → void
`get(key)` resolves a player by EITHER its account userId (the natural, exposed key) OR its internal identity entity id (internal callers). Scans the identity registry — only registered (connected) players resolve.
| arg | type | description |
|---|---|---|
| key | ? |
ownerOf(avatar: ?) → void
`ownerOf(avatar)` — the connected player whose body is this avatar. Accepts the avatar entity REF (the normal `player.avatar` value) or its id string. Resolves through the synced `UserIdentity.avatar` tie: scan the registry for the player pointing at this avatar. Works on every peer because the identity entities replicate into the room. nil when the avatar belongs to no registered player.
| arg | type | description |
|---|---|---|
| avatar | ? |
onLocalReady(cb: ?) → void
| arg | type | description |
|---|---|---|
| cb | ? |
onPlayerJoined(cb: ?) → void
| arg | type | description |
|---|---|---|
| cb | ? |
onPlayerLeft(cb: ?) → void
| arg | type | description |
|---|---|---|
| cb | ? |
offLocalReady(h: ?) → void
| arg | type | description |
|---|---|---|
| h | ? |
offPlayerJoined(h: ?) → void
| arg | type | description |
|---|---|---|
| h | ? |
offPlayerLeft(h: ?) → void
| arg | type | description |
|---|---|---|
| h | ? |
count( ) → void
Short-form aliases. The getting-started doc + the test suite use the shorter names; the longer names stay for back-compat. Both shapes call the same registry, so subscribers added via either name fire together.
exists(id: ?) → void
| arg | type | description |
|---|---|---|
| id | ? |
onJoin(cb: ?) → void
| arg | type | description |
|---|---|---|
| cb | ? |
offJoin(h: ?) → void
| arg | type | description |
|---|---|---|
| h | ? |
onLeave(cb: ?) → void
| arg | type | description |
|---|---|---|
| cb | ? |
offLeave(h: ?) → void
| arg | type | description |
|---|---|---|
| h | ? |
_addPlayer(eid: ?) → void
| arg | type | description |
|---|---|---|
| eid | ? |
_removePlayer(eid: ?) → void
| arg | type | description |
|---|---|---|
| eid | ? |
reconcile( ) → void
fireLocalReadyOnce(eid: ?) → void
Latch-controlled local-ready fan-out. Routed through two distinct entry points so the trigger conditions are explicit: _markLocalAvatarOptOut(): scene declared `avatar_<mode> = ""`, so the player_spawner won't bind an avatar; fire onLocalReady as soon as the UserIdentity component is attached. _onLocalAvatarBound(eid): the player_spawner finished binding an avatar to the local identity; fire onLocalReady now. Both call this single fan-out which is idempotent — the latch protects against double-fires when an avatar rebind happens after the initial ready.
| arg | type | description |
|---|---|---|
| eid | ? |
_markLocalAvatarOptOut(eid: ?) → void
| arg | type | description |
|---|---|---|
| eid | ? |
_onLocalAvatarBound(eid: ?, avatarEid: ?) → void
| arg | type | description |
|---|---|---|
| eid | ? | |
| avatarEid | ? |
_fireLocalReady(eid: ?) → void
Legacy callout retained because player_lifecycle still routes through it at the identity's awake time. The latch keeps the fire from happening too early (avatar not yet bound) — callers should prefer the explicit `_onLocalAvatarBound` / `_markLocalAvatarOptOut` triggers above. Kept as a no-op-when-not-ready stub for API compatibility.
| arg | type | description |
|---|---|---|
| eid | ? |
localRegisteredEntityId( ) → void
Resolve the local player's internal entity id through the IDENTITY REGISTRY. The local player is the identity THIS session OWNS — an `isLocal` identity — so ownership is the primary key: it uniquely names this session's identity even when several sessions share an account (identical `userId`), where a userId match alone is ambiguous and could return a remote peer's identity. The userId match is the fallback for the brief window before `isLocal` has resolved on a fresh join; the module-level `localPlayerEntityId` is the last resort before the registry entry has landed.
__index(_: ?, k: ?) → void
| arg | type | description |
|---|---|---|
| _ | ? | |
| k | ? |
__newindex(_: ?, k: ?, _: ?) → void
| arg | type | description |
|---|---|---|
| _ | ? | |
| k | ? | |
| _ | ? |
Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.