Live events and synchronized state
Watch bot activity and keep a local copy of the Minecraft world state.
bot.events() returns the ordered server stream.
bot.observe() reads that stream and maintains an immutable state model.
Use the raw stream for temporary activity such as sounds and particles.
Use a session for retained state such as entities, blocks, weather, and scoreboards.
Read state after managed setup
createBot, getOrCreateBot, and bot.connect() already attach a synchronized session and wait for the initial player snapshot.
Python provides the same behavior through create_bot, get_or_create_bot, and bot.connect().
Read bot.state without opening another observer:
yield* Effect.logInfo(bot.state.player);from effect_py import sync
yield from sync(lambda: print(bot.state.player))bot.state contains the latest received data. It can change between reads.
Before connection, or while replacement snapshots arrive after reconnect, player state can be absent.
Use bot.observe() when you need an explicit session handle, custom filters, or state predicates.
Watch an entire instance
instance.events() combines events from selected bots into one connection.
Each BotEvent item identifies its bot with botProfileId or bot_profile_id.
Use this stream for fleet telemetry, shared chat, dashboards, and coordinators.
yield* instance.events({
botEvents: {
includeChat: true,
includeLifecycle: true,
includeStateDeltas: true,
},
botIds: selectedBotIds,
}).pipe(
Stream.runForEach((item) =>
Effect.logInfo("bot event", {
botId: item.botProfileId,
event: item.event,
})
),
);The default instance filter includes every stateful category and uses the
server's bounded entity and block radii. Sounds and particles remain opt-in.
On subscription, each selected bot contributes current status and snapshots
before live changes. Pass botIds in TypeScript or bot_ids in Python to
limit the stream to a stable profile UUID allowlist.
Open a synchronized session
const session = yield* bot.observe();
const hostile = session.state.entitySnapshots.get(networkId);
const raining = session.state.environment.raining;
yield* session.events().pipe(
Stream.filter((event) => event.event.case === "damage"),
Stream.runForEach((event) => Effect.logInfo("damage", event)),
);The default session filter includes state, lifecycle, chat, damage, inventory, entities, nearby block updates, environment changes, the player list, boss bars, and scoreboard changes. Sounds and particles are opt-in because busy servers can emit them at a high rate. Resource-pack offers and removals are included by default. Titles are also included by default because they often carry gameplay instructions. Chunk events remain opt-in because movement can produce a large stream.
Match chat without managing a session
bot.chat.waitFor matches a substring, regular expression, or predicate and
returns the semantic chat event plus regular-expression captures. Limit a
matcher to player chat, whispers, system messages, or action-bar messages when
the same text can appear in several channels.
const match = yield* bot.chat.waitFor(
/code (?<code>\d{4})/u,
{
sources: [ChatSource.SYSTEM],
timeoutMs: 10_000,
},
);
yield* Effect.logInfo("received code", {
code: match.groups.code,
});Use bot.chat.watch(...) for every matching message.
TypeScript returns an Effect Stream.
Python returns a stream with effect-based consumers such as run_for_each.
State indexes
The session maintains:
- Full semantic entity snapshots, including velocity, equipment, effects, attributes, ownership, passengers, targets, and dropped items.
- Full block snapshots, including state properties, light, biome, fluid, hardness, tool hints, and interaction metadata.
- The current player list with latency, game mode, display name, and list presentation fields.
- World clocks, rain state, rain level, thunder level, and the latest generic game event.
- Incrementally merged boss bars.
- Objectives, display slots, scores, teams, and team membership.
- Pending resource-pack offers with IDs, URLs, hashes, requirements, and prompts.
- Player, inventory, connection status, stream epoch, and revision metadata.
Entity network IDs are valid only for one connection epoch. Use each
EntitySnapshot.reference.connectionEpoch with its network ID when caching a
target beyond the current callback.
Choose event categories
const session = yield* bot.observe({
filter: {
includeEntityEvents: true,
entityRadius: 64,
includeBlockUpdates: true,
blockRadius: 24,
includeEnvironment: true,
includePlayerList: true,
includeBossBars: true,
includeScoreboard: true,
includeResourcePacks: true,
includeTitles: true,
includeChunks: false,
includeSounds: true,
includeParticles: false,
},
});Python accepts the generated BotEventFilter message through
BotSessionOptions.
The server sends an initial player, inventory, entity, environment, and player-list view when a session attaches to an already connected bot. Sequence gaps or epoch changes clear synchronized state before replacement snapshots are applied.
Observe titles and chunk loading
Title events preserve title, subtitle, animation timing, clear, and reset as distinct event kinds. Chunk events report loaded and unloaded columns with their dimension and chunk coordinates.
const stream = bot.events({
includeTitles: true,
includeChunks: true,
});
yield* stream.pipe(
Stream.runForEach((event) =>
Effect.logDebug("transient event", { event: event.event })
),
);
const loaded = yield* bot.waitForChunks({
radiusChunks: 2,
timeoutMs: 30_000,
});from soulfire import BotEventFilter
from effect_py import fn, sync
@fn("consume stream")
def consume(event):
yield from sync(lambda: print(event.WhichOneof("event")))
yield from bot.events(
BotEventFilter(include_titles=True, include_chunks=True)
).run_for_each(consume)
loaded = yield from bot.wait_for_chunks(
2,
wait_timeout_ms=30_000,
)waitForChunks and wait_for_chunks run on the SoulFire server. They inspect
the bot's current chunk as it moves and finish only when the complete square
radius is present. Cancelling the SDK operation cancels its pending server
wait. The maximum supported radius is 16 chunks and the maximum server wait is
five minutes.
Build custom stores and replay tests
Both SDKs expose their reducers independently from the live session.
let state = emptyBotSessionState();
for (const event of recordedEvents) {
state = reduceBotSessionState(state, event);
}state = empty_bot_session_state()
for event in recorded_events:
state = reduce_bot_session_state(state, event)These pure reducers are useful for Effect SubscriptionRef integrations,
Redux-style stores, deterministic event replays, and testing automation
without a live Minecraft server.
How is this page?
Last updated on
