Skip to main content

Discovery

sync.discovery finds the other bedrock-core nodes in the world and keeps a live list of them.

Script events are ephemeral: a pack that loads after another has already announced never hears that announce. Discovery solves that two ways — every node re-announces on a heartbeat, and a freshly started node broadcasts a whois that prompts existing peers to announce straight back. A TTL sweep drops peers that go quiet.

Import

import type { PeerInfo, CollisionInfo, PeerListener, CollisionListener, DiscoveryOptions } from '@bedrock-core/sync';

Usage

sync.discovery.onPeerUp(peer => console.warn(`${peer.id} v${peer.version} joined`));
sync.discovery.onPeerDown(peer => console.warn(`${peer.id} left`));

const peers = sync.discovery.peers;
const economy = sync.discovery.getPeer('drav0011_economy');

From a runtime, the same information arrives pre-interpreted through core.registry, which turns each peer's meta into an addon manifest. Reach for core.node.discovery only when you want the raw peer records.


PeerInfo

interface PeerInfo {
id: string;
version: string;
schemaVersion: number;
meta?: Record<string, unknown>;
lastSeen: number;
}
FieldMeaning
idThe peer's node id — its transport address.
versionWhatever it passed as version, or '0.0.0'.
schemaVersionWhatever it passed as schemaVersion, or 0.
metaThe opaque blob it attached to its announce. server-runtime puts the addon manifest plus runtimeVersion here.
lastSeenThe tick this peer was last heard from.

API

peers

sync.discovery.peers: PeerInfo[]

Known live peers. Excludes self.

getPeer

sync.discovery.getPeer(id: string): PeerInfo | undefined

onPeerUp

sync.discovery.onPeerUp(listener: PeerListener): Unsubscribe

Fires when a peer is seen for the first time. Subsequent heartbeats from the same peer refresh lastSeen without re-firing.

onPeerDown

sync.discovery.onPeerDown(listener: PeerListener): Unsubscribe

Fires when a peer expires — its lastSeen fell outside the TTL window during a sweep. Note that this is a timeout, not a graceful goodbye: there is no "leaving" message, so a peer that stops answering takes up to the TTL to disappear.

onCollision

sync.discovery.onCollision(listener: CollisionListener): Unsubscribe

interface CollisionInfo {
id: string;
instanceId: string;
}

Fires when another instance is announcing our own id — a namespace collision. Such an announce is surfaced here rather than being stored as a peer.

At the runtime layer this is core.registry.onNamespaceCollision.

announce

sync.discovery.announce(): void

Broadcast this node's presence immediately. start() does this for you and the heartbeat repeats it; call it by hand only if you changed something peers need to see sooner than the next beat.

whois

sync.discovery.whois(): void

Ask every peer to announce itself. Sent once at start(). Peers reply directly to the asker rather than broadcasting, so a late loader joining a busy world does not spam everyone.


Timing

ConstantValueMeaning
ANNOUNCE_INTERVAL_TICKS100Heartbeat period — every node re-announces every 5 s.
PEER_TTL_TICKS320A peer unheard from for 16 s is considered gone.
Sweep interval40 ticksHow often the TTL check runs.

So a peer that disappears is detected somewhere between 16 and 18 seconds later, and a peer that appears is normally visible within a tick or two of its whois reply — but never assume a peer exists on tick 0. React to peers through events (onPeerUp, onDependenciesSatisfied, a feature condition) rather than by checking once at startup.

DiscoveryOptions lets a test override announceIntervalTicks and peerTtlTicks.