This guide walks through swapping the Python reference for the native PyO3 façade. The goal is identical observable behaviour at higher speed with a single-line code change.
# Dev (build from source):
maturin develop --manifest-path python-ext/Cargo.toml
# Release (once wheels are published):
pip install minecraft_bot_accelThe native package is separate from minecraft_bot. Both can be
installed side-by-side; importing one does NOT import the other.
Before (Python reference, 002):
from minecraft_bot.bot import Bot
async def main():
async with Bot.offline("host", 25565, "Greeter") as bot:
await bot.walk_to(x + 5, y, z)After (PyO3 façade, 003):
import minecraft_bot_accel as mb
async def main():
bot = mb.Bot.offline("host", 25565, "Greeter")
await bot.connect()
try:
pos = await bot.position()
await bot.walk_to(pos[0] + 5, pos[1], pos[2])
finally:
await bot.disconnect()That's the entire migration. The async surface, the property names, and the return types are identical.
The accel façade is growing the Python reference's surface incrementally. As of 003 it exposes:
Bot.offline(host, port, username) -> Botawait bot.connect(),await bot.disconnect()await bot.position()->(x, y, z, yaw, pitch)orNoneawait bot.entity_id(),await bot.health(),await bot.food()await bot.walk_to(x, y, z, *, timeout=30.0)await bot.walk_to_blind(x, y, z, *, timeout=30.0)(diagnostic)await bot.drop_held_item(*, full_stack=False)await bot.send_raw(payload: bytes)(raw packet escape hatch)bot.loaded_chunk_count()(sync)bot.world->Worldview (sync)world.get_block_id,get_block_name,is_solid,is_waterworld.find_blocks_nearby(name, origin, *, radius, limit)world.apply_block_change,apply_map_chunk,apply_unload_chunkworld.dimension,min_y,section_count
minecraft_bot_accel.codec.{Reader, Writer}minecraft_bot_accel.codec.varint.{read, write, encoded_size, read_many, write_many}minecraft_bot_accel.codec.varlong.{read, write}minecraft_bot_accel.codec.nbt.{read, read_bytes, write, TAG_BYTE..TAG_LONG_ARRAY}minecraft_bot_accel.framer.Framerminecraft_bot_accel.pathfinding.find_path(world, start, goal, ...)minecraft_bot_accel.physics.{tick, tick_n, PhysicsState, PhysicsIntent}minecraft_bot_accel.observation.{Vec3, Observation}minecraft_bot_accel.entities.Entityminecraft_bot_accel.effects.{StatusEffect, effect_name, effect_id}minecraft_bot_accel.ItemStackminecraft_bot_accel.errors.*(full exception hierarchy: 23 classes)minecraft_bot_accel.WireLogawait bot.on_packet(packet_id, callback)plusawait bot.clear_hooks()await bot.walk_to_blind(...)diagnostic mode
Surface still landing in later phases:
- Live
bot.inventorytracker mirroring the Python WindowItems flow - Live
bot.entitiesnearby-entity tracker - Chat / behaviour trees / dig
Measured 2026-05-12 on Linux x86_64 WSL2, Python 3.12.3, Rust 1.94.0, release build via maturin.
| Operation | Python | Accel | Speedup |
|---|---|---|---|
| End-to-end chunk burst (decode + cache + find_blocks) | 0.218 s | 0.007 s | 31.44× |
| Chunk decode alone (48 KiB payload) | 345 µs | 121 µs | 2.84× |
| VarInt batched read (N=1000 per call) | 112 ms | 4 ms | 26.82× |
| VarInt batched write (N=1000 per call) | 76 ms | 3 ms | 24.68× |
| NBT decode (638 B real heightmaps) | 13 ms | 4 ms | 3.26× |
| Physics tick (batched, N=50 per call) | 49 ms | 6 ms | 8.38× |
| A* pathfinder (30 random pairs on 32×32 grid) | 79 ms | 12 ms | 6.38× |
| VarInt single read (per-call) | 747 ns | 2095 ns | 0.36× (FFI dominates) |
| VarInt single write (per-call) | 671 ns | 2442 ns | 0.27× (FFI dominates) |
Heavy operations and batched primitives win big. Per-op codec calls
on 1-2 byte values cross the FFI boundary every call and lose to
pure Python. Use the batched APIs (varint.read_many,
varint.write_many, physics.tick_n) when you have many values to
process at once. Bot.walk_to and Bot.dispatcher already amortise
the boundary internally so callers do not have to think about it.
You can hold instances of both backends at the same time:
import minecraft_bot
import minecraft_bot_accel as mb
bot_py = minecraft_bot.bot.Bot.offline(...)
bot_ac = mb.Bot.offline(...)They don't share state. Public types are structurally identical but not type-identical:
isinstance(o, minecraft_bot.Observation) # works on py-bot return
isinstance(o, minecraft_bot_accel.Observation) # works on ac-bot return
# Cross-package isinstance returns False - by design (Constitution VI).Compare by content (.to_dict() / field-by-field) in parity
tests; never by isinstance across packages.
After switching:
# Run your existing test suite against accel:
pytest --backend accel
# Or use the parity harness from this repo:
pytest tests/python/parity -m "not live"
# Live integration:
pytest tests/python/parity -m liveIf any test fails against accel but passes against python, the behaviour gap is a parity bug - please file an issue with the fixture / live capture.