Milestones
List view
**Why.** A crate depends only on the layers below it, and a user who takes a prefix of the layers pays for nothing above it. Today the server crate holds the engine beside vanilla parts, world generation crates depend on the engine and the wire, the world crate links all of world generation, the maintainers' update tool sits among the library crates, and most library crates can panic. **Scope.** The crate moves below, together with #59, #60, #513 and #514. **Done when.** A server without world generation, Anvil and light builds from the engine and network crates alone, and every library crate denies the panic lints. This milestone serves the library direction (#180).
No due date•7/8 issues closed**Why.** A lobby, a proxy or a minigame server must accept the clients players use today, down to 1.21.6, while the implementation stays on one version. **How.** A chain of hand-written steps between adjacent versions, installed at the network edge through a frame transform seam; the engine sees only the targeted version. **Done when.** A 1.21.6 vanilla client joins the mcrs server. This milestone serves the library direction (#180).
No due date•0/3 issues closed**Why.** The design for one dimension on many nodes (#181) needs seams in the engine that cost nothing with one process and one node: sections as the shared index, shards over simulation kernels without the engine, global state as one snapshot, frames encoded once and delivered by subscription, a visibility budget, wire entity ids that do not depend on the world, and one registry of persisted components. Every server gets them, and gameplay is built on them. **Scope.** In: #152, #153, #154, #155, #156, #158, #159, #160, #460. Out: distribution across processes and nodes, which stays in Scaling one dimension.
No due date•0/9 issues closed**Why.** Vanilla lights everything with one warm block light. Coloured light gives a torch, a soul lantern and an amethyst cluster their own colour, and dynamic light makes a torch in hand light the world. The light volume, its GPU kernel and its feeder are built and live in their own crate behind one seam, but the client never adds the plugin, so the released client lights with the vanilla tint, and nothing about the volume has been checked on the deferred path, in a browser or on entering the Nether. A torch in hand lights nothing, although the item-to-light map exists as data (`assets/mcrs/item_light/minecraft.json`). This milestone serves the library direction (#180). **Scope.** Coloured light reusing the base lightmap maths (#345); verifying static coloured light and enabling it by default (#346); colour across the whole render distance (#347); held light items around the local player (#348); recolouring when an emitter is placed or broken (#349); lights carried by other entities (#350). Out: applying block edits on the client (#29) and tracking entities (#135), which #349 and #350 need. **How.** - Light is computed in deferred passes, and colour never forces a mesh rebuild: a recolour touches only the affected bricks of the volume. - Dynamic light is render-only and never touches gameplay. A held item lights like the same block placed at that spot; the level is the higher of the two hands, and an emitter whose light origin is in water emits nothing. The number of sources is budgeted from data, and sources past the budget fade out. - A full-resolution atlas at 96 columns would need about 7.7 GiB in the Nether and about a minute of GPU work to colour; the target is 150 to 200 MiB natively, with far tiers, constant pages for regions of one light type, and colouring nearest first under a per-frame budget. - Every figure is taken natively and in a WebGPU browser. Start with #345, then #346. #348 starts by measuring the recompute policies at 1, 16, 64 and 256 lights; #349 and #350 wait on #29 and #135. **Done when.** Coloured light is on by default, each emitter lights in its own colour with smooth section borders, 64 moving sources fit the frame budget natively and in a browser, and placing, breaking and carrying lights recolours the world with no mesh rebuild. **Decisions** - #401 Coloured light lives in its own crate and meets the renderer through one bind group and one shader function - #402 Coloured light is propagated per light type on the GPU from section bricks built with the server's light rule - #403 Coloured and dynamic light only tint the server's light; levels, gameplay and the F3 readout stay vanilla - #404 Block light colours and the item-to-light map are data assets validated at load
No due date•4/10 issues closed**Why.** The client is meant to run in a browser over WebGPU as well as natively; the server already opens a WebTransport endpoint for it. The web build does not work: `scripts/build-web.sh` makes a bundle that Chrome runs and that draws the sky, but start-up blocks the page's main thread for 87 seconds on the first load and more than eight minutes after a reload, and the render world's start-up schedule runs every frame where it runs once natively. Until it reaches a steady frame no web figure can be taken, so the renderer's budget is checked natively only. This milestone serves the library direction (#180). **Scope.** One issue, #344: start-up that yields to the page, a render start-up schedule that runs once, a decision on baking every block state at load on wasm, and the web figures in `PERF.md`. Out: the WebTransport endpoint's option and certificate renewal (#309), the tint window that caps the web render distance (#35), baking itself (#24). **How.** - Loading, reading the resource pack and baking run without blocking the page's main thread, including on a single-threaded task pool. - There is no path without WebGPU. Pipelines stay within WebGPU's default limits, such as eight storage buffers per shader stage; the browser, not naga, rejects a pipeline that exceeds a capability, asynchronously and at creation, so every new pipeline is checked in a browser as well as natively. - The native and web builds assemble one plugin group (#28), so optional resources are not checked case by case. - Both targets build on every change; a regression in the web build is a failure, not a later fix. - Figures from the browser are the CPU stages and the GPU pass timestamps, taken the same way as natively. Start with the start-up schedule, which makes every later measurement possible, then the blocking load, then the bake decision once #24 has a native figure. **Done when.** Chrome reaches a steady frame on a joined world, and the CPU stages and GPU pass timestamps of the web build are in `PERF.md`.
No due date•1/2 issues closed**Why.** The engine makes promises a library user and the scaling design rely on: the same inputs give the same tick whichever thread ran first, no system stops every other system, and the memory a server costs per loaded section and per entity is known and bounded. Nothing enforces them today. No schedule sets ambiguity detection, so two systems that write the same data can run in either order. Twelve item and inventory systems in each dimension's tick take `&mut World`, because an inventory transaction applies through it. Only `Id<Block>`, `AttributeValue` and three mob variants have their size pinned, and sections, light and queues do not report their size in bytes, although a hundred bytes more per section is megabytes per player. This milestone serves the library direction (#180) and is a precondition of regions running in parallel (#160). **Scope.** Simulation schedules that fail on order ambiguity (#323); item and inventory systems without exclusive world access (#322); pinned sizes for per-section and per-entity components and a count of section memory (#324). Out: the idle tick and overruns (#320), the client's memory budget (#337), load measurements (#151). **How.** - No system takes `&mut World` outside the seams: extraction between worlds, a queued command, driving a dimension world, a test fixture. - Ambiguities are resolved by ordering or by splitting data, never by a blanket ignore. - Every component carried by every section or every entity has an `assert!(size_of::<T>() <= N)`. Large owners state their heap size in bytes, the way `BlockDefinitions` does, and every retained collection has an owner and a release condition. - An exception to any of these is justified by a number in `PERF.md`. #322 starts with a decision: keep `Transaction` as a single writer through `&mut World` as a stated exception, or move it to commands and system parameters; the answer sets the size of the issue. #323 can land first and stays green from then on. **Done when.** A test builds the main app and a dimension world with the full server plugin and every simulation schedule reporting ambiguities as errors and passes; none of the twelve systems takes `&mut World`; and `PERF.md` states bytes per loaded section with light, from a test that counts them. **Decisions** - #363 A section's lifecycle is one stage value and a numeric ticket level, and every other predicate is derived - #364 The tick runs in phases with few sync points, each system one transformation over every entity it names - #365 Work is proportional to change, and sparse changes over a large population go through an explicit work queue - #366 No system takes exclusive world access outside the seams - #369 The server's memory is four budgeted terms, and every multiplied type has a pinned size - #370 Positions are typed with the reference's names in the core crate, and hot loops keep raw integers - #371 There is no game-agnostic voxel layer; the world crates are Minecraft crates - #417 The game's sources are the authority on data, never on code shape
No due date•10/11 issues closed**Why.** The repository has no `.github/` directory: formatting, lints, tests and the layer checks are held by local hooks that are not versioned, so nothing stops a change that breaks them from reaching `main`. Several public documents describe a design the code left behind. An idle server's tick has never been measured, and a tick that overruns is not even counted. And a user outside the repository cannot take the crates from a registry: two crates embed files from outside their own directory, and the protocol and network crates return `anyhow` errors. This milestone serves the library direction (#180). **Scope.** Continuous integration (#318); removing stale documents (#319); measuring the idle tick and reporting overruns (#320); making the protocol, network and game data crates publishable (#321); removing unused workspace dependencies (#80). Out: the engine rules that tests enforce (Engine conformance), load measurements (#151). **How.** - On every pull request: `cargo fmt --check`, clippy, `cargo nextest run --workspace` with the default profile, `scripts/check-no-bevy.sh`, and a build of the protocol and data crates for `wasm32-unknown-unknown`. The full profile runs on a schedule. - Tests come in two tiers: the default profile skips everything under `exhaustive::` and kills a test after a minute; sweeps over the corpus, seeds or large regions run in the full profile. Tests that need the game jar or a GPU run where those exist, or are skipped there by a stated rule. - The nightly toolchain is pinned to a date; `rust-toolchain.toml` names `nightly` with none today. - A document under `docs/` names only crates and types that exist and cites the targeted version (`assets/minecraft/version.json`). - #321 starts with a decision: whether publishing and a stable build are goals at all while the project follows one snapshot at a time, and whether typed errors come before online mode or with it. Start with #318, which guards everything after it; #80 and #319 are small and independent. **Done when.** A pull request cannot be merged while a check is red, no document describes code that does not exist, and `PERF.md` states the idle cost of the main app and of each dimension world. **Decisions** - #412 One targeted version at a time, with no backward compatibility - #413 The game's own version.json is the only statement of the version - #414 The update tool alone writes the data taken from the game - #415 Parity fixtures are captured from the game's own code and never edited by hand - #438 Tests run under nextest in a default and an exhaustive tier, one integration binary per crate
No due date•16/19 issues closed**Why.** Changing a datapack needs a restart. The registries a reload reads already load as a second stage over the world registries (#327), but nothing runs that stage again, and clients keep the old tags and registry data. Reloading while the server runs is what datapack authors and server operators expect from `/reload`, and it is wider here than in vanilla: values of world registries whose entry names do not change may be replaced too. This milestone serves the library direction (#180). **Scope.** Reloading data packs while the server runs (#316) and sending players the reloaded tags and registry data (#317). Out: stacking packs with overrides (#315), packs embedded by a crate (#286), the sync and worldgen loading it depends on. **How.** - Registries are immutable values. `/reload`, or enabling or disabling a pack, starts a clean load off the tick; the new set replaces the old one whole at a tick boundary, in the main app and in every dimension world. - Each derived table is rebuilt by its own owner when the set is replaced. A design that needs an `update_*`, `invalidate_*` or `apply_to_*` method pushing new values into another owner's state is rejected. - A reload that touches only tags and reloadable registries reruns only the second stage. A chunk whose generation has started finishes on the old set. - Changes the saved world depends on (a new biome, a dimension type's height, anything that would change ids held in running state) are refused with a report, and the old data keeps running. A broken file leaves the running data in place. - Players receive `update_tags` after every reload; when synced values changed they are sent back to the configuration state for the new data, and our client replaces its set whole. What a vanilla client accepts when sent back to configuration during play is the first thing to check. This is blocked by server and client sync (#52, #53) and by the worldgen registries (#55). **Done when.** A change to a tag, a loot table and a biome's value takes effect at one tick boundary in every world; adding a biome or changing a dimension type's height is refused; a broken file leaves the running data in place; and a vanilla client and our client stay connected through a reload and see the new tags. **Decisions** - #427 Registries are immutable values, replaced only as a whole
No due date•1/3 issues closed**Why.** A bot, a load test with a thousand players and a behaviour test that plays as a client all need the client without a window. Today the client crate requires `bevy_render`, `bevy_winit`, `bevy_pbr` and the UI crates, and depends on the render and mesh crates unconditionally, so a bot builds a renderer it never uses and a headless test of client behaviour needs a GPU. This milestone serves the library direction (#180); the load measurements of #151 and the player scenarios of the behaviour tests reuse it. **Scope.** Client world state that builds and runs without rendering (#301), and two bots that exercise it end to end: a walker that moves, breaks a block and moves an item between slots, and a test bot that obeys chat commands from its owner (#302). Out: the movement model shared by server and client, which belongs to the player movement work; block edits and entity tracking on the client (#29, #135); the public login without the engine (#303), which a bot below the engine uses instead. **How.** - The client is network, world state and input. Rendering is a plugin and a cargo feature the client adds, never a dependency of world state: no world-state type names a render or mesh type. - A bot is the client with rendering off, not a fork of it. Whatever a bot needs (teleport confirmation, movement, inventory) runs without a window or UI. - The dependency tree of the headless build is checked by the example harness (#288), with `bevy_render`, `bevy_winit` and `bevy_pbr` forbidden. Start with #301; #302 follows it. **Done when.** The client builds with rendering off with none of `bevy_render`, `bevy_winit`, `bevy_pbr` or the render crates in its tree, joins the embedded server in a test without a window, and both bots complete their scripted runs with every position accepted without correction.
No due date•1/2 issues closed**Why.** The crates are meant to be usable from the bottom up: protocol and data without Bevy or tokio, network without Bevy, game data and Anvil without the engine, rendering from data alone. Today that is held by convention and by `scripts/check-no-bevy.sh`, which checks one property for a fixed list of crates. Nothing proves that a bot builds without Bevy or that a world tool builds without tokio. Each example is a program that takes one prefix of the layers and proves two things: the boundary, by a test on its dependency tree, and a behaviour, by a test against our own server, client or proxy. This milestone serves the library direction (#180); the proxy core it produces is also the edge core of #181. **Scope.** The harness (#288). Network: the packet inspector (#289), the status ping (#290), the echo chat bot (#293), Microsoft account sign-in (#294), the typed cookie, transfer, custom query and player chat packets they need (#295), and the proxy library (#296). Data: reading and editing a saved world (#291) and checking a user datapack (#292). Rendering from data: map tiles from a region file (#297), a structure and model viewer (#298), a worldgen preview by seed (#299). Comparison against a vanilla server (#300). Out: the engine examples (#287), the walker and test bots (#302), the load driver (#151), the edge as a process (#163). **How.** - Each example is a workspace member under `examples/<name>/`, not `cargo --example`: an example inside a package shares its dev-dependencies, and the tree check would prove nothing. - One test reads `cargo metadata`, resolves each example's normal dependencies and checks them against one table of forbidden crates, failing with the example and the path to the crate. - Behaviour tests live in the example's `tests/it/` and share an embedded server or a fake session server through a `LazyLock`. Runs against the vanilla server need Java and the game jar and live in the full profile. - An example appears when it proves a boundary or fills a gap, not one per feature. Start with #288, then #289, #290, #291 and #292, which need little new code. The bot and proxy wait on the public login (#303) and online mode (#82). **Done when.** Every example builds within its forbidden list and passes its behaviour test, and the bot and the proxy pass against the vanilla server of the targeted version (`assets/minecraft/version.json`).
No due date•1/13 issues closed**Why.** Advancements ship as data and their wire types exist, but the advancement registry is never loaded, and statistics are not counted. Advancements guide new players and unlock recipes (#182). **Scope.** In: advancements loaded typed, criteria tracked per player, rewards granted (#257). Later issues here: statistics counted, saved and sent with `award_stats`, and the recipe unlocks that advancements give. Out: the recipe book itself (#210). **How.** - Lands as removable vanilla plugins, never in the server crate. - Validated at load: every criterion trigger and condition is read typed when the registry loads, and an unknown trigger fails the load instead of the first time it would fire. - Work follows facts: triggers are messages from the systems that already know the fact (inventory changed, block placed, entity killed, dimension changed), and a trigger visits only the players with an open criterion of its type, never every player. - Determinism: scheduler-independent and without randomness of its own, so completion is compared exactly; loot rewards roll through the loot evaluator (#72). - Progress is player state saved in the vanilla layout through the persisted component registry (#159), so it survives a relog and a dimension change; the tree and its changes reach the client through `update_advancements`. - Tests: behaviour tests in which a mock player (#274) completes criteria through the production messages, and a vanilla client that shows the tree. - Start with #257, once item use (#117) and the mock player exist. **Done when.** Picking up a crafting table completes `story/root`, the progress survives a relog, and a vanilla client shows the tree with that progress.
No due date•0/2 issues closed**Why.** Armour stands, item frames, paintings and display entities are part of every build and every minigame map, and none can be placed or used. Display and interaction entities are how maps draw holograms and clickable areas, so a minigame built on the library needs them without the rest of vanilla (#180, #182). **Scope.** In: armour stands (#253); item frames, paintings and leash knots (#254); display and interaction entities (#255). Out: leads (#235) and the items shown in frames (#123). **How.** - Lands as removable vanilla plugins, never in the server crate; display and interaction entities work with every other gameplay plugin removed. - Determinism: scheduler-independent. Hits within the breaking window and uses are ordered by arrival; a painting's variant is random among those that fit and compared by distribution; a frame's comparator output and popping when the support goes are exact. - Data: painting variants come from the `#placeable` tag or the item's fixed variant; display transforms and flags from entity data and commands. - Network cost: a display has no gravity, never despawns and does not count in caps; a change of its data sends only that change, encoded once for every viewer (#155), with wire ids that do not depend on the owning world (#158). - Builds on #159, so a summoned display keeps its transform after a restart, and on shape updates (#185) for popping. - Tests: behaviour tests against the vanilla test server's outcomes (#272), with a mock player (#274) dressing and hitting. - Start with #255, which needs no combat; #253 and #254 need damage (#106) and item use on entities. **Done when.** A stand dressed with a helmet shows it to a second player and two quick hits drop both, a frame rotated three times gives a comparator 4, and an interaction entity reports the player who used it.
No due date•0/4 issues closed**Why.** Minecarts and boats can be generated but not used. Rails move players, mobs and items in most bases, and boats cross oceans and, on ice, are the fastest transport (#182). **Scope.** In: minecarts on rails, with powered rails (#250); boats (#251). Later issues here: chest, hopper, furnace and TNT minecarts, activator and detector rails, chest boats and rafts. Out: mounting and dismounting (#100) and horses (#237). **How.** - Lands as removable vanilla plugins, never in the server crate. - Determinism: a boat is driven by its rider's client and reported through `move_vehicle`; the server checks the report with the same arithmetic as vanilla and sets it back when impossible, like player movement, so this part is exact. Minecarts move on the server, without randomness, and their speeds and positions are compared exactly with the vanilla test server's. - The new minecart behaviour applies only with the `minecart_improvements` feature flag; without it the default behaviour of the reference holds. - Rails shape themselves through shape updates (#185), powered rails read the power model (#191), and a minecart finds mobs to pick up through the area query (#98). - Builds on Entity physics (#97, #100), the movement model and its check (#85, #94), and #158 for passengers whose wire ids do not depend on the owning world. - Start with #250 once #97, #100 and #191 land; #251 needs the vehicle movement check. **Done when.** An empty minecart on a powered line reaches 0.4 blocks per tick and stops on an unpowered powered rail, and a boat on packed ice outruns one on water at vanilla's speeds.
No due date•0/3 issues closed**Why.** The End has no dragon, so the game cannot be finished and its gateways never open. No boss bar reaches any client (#182). **Scope.** In: boss bars for the players who see the boss, with `/bossbar` (#247), and the ender dragon fight (#248). Later issues here: the wither and the warden. Out: End portals and gateways (#144, #145) and the raid state behind the raid bar (#245). **How.** - Lands as removable vanilla plugins, never in the server crate. The `boss_event` packet is typed in the protocol crate on its own, so a library user can show a bar without vanilla (#180). - A boss bar holds its viewers; entering and leaving range add and remove it, and a change is encoded once and sent to every viewer (#155). - The dragon's phases are systems over the dragon's parts, not a phase object per dragon. The fight state is saved with the dimension. - Determinism: scheduler-independent. Phase choice is random in vanilla and compared by distribution; the end of the fight (exit portal, egg the first time, experience, one gateway) is exact and compared with the vanilla test server's outcome (#272). - Builds on Mob AI, Entity physics and Health and combat (#106). - Start with #247; #248 waits for mob movement and damage. **Done when.** A dragon damaged to death opens the exit portal and spawns one gateway, the fight state survives a restart, and a custom bar follows a player in and out of its range on a vanilla client and on ours.
No due date•0/3 issues closed**Why.** Hostile mobs are generated and do nothing, so the night is safe. Slime farms and raid farms rest on rules players rely on to the block and to the tick (#182). **Scope.** In: melee and ranged attacks, targeting and burning in daylight (#243); slime chunks (#244); raids and their wave state machine (#245). Later issues here: zombies, skeletons, creepers, spiders, endermen, witches, blazes, slimes splitting, piglins, guardians, shulkers, phantoms and the other families, and patrols. Out: bosses (Bosses) and spawning (Mob spawning). **How.** - Lands as removable vanilla plugins over Mob AI, never in the server crate. - Determinism: slime chunks are exact, a pure function of the world seed and the chunk with no state, tested against chunks recorded from the reference for several seeds. Raid timers and wave counts are exact; raid composition, targeting and hits are compared with the vanilla test server's distribution. - Data, not code: burning in daylight follows `minecraft:gameplay/monsters_burn`, surface slimes `minecraft:gameplay/surface_slime_spawn_chance` and the biome tag `#allows_surface_slime_spawns`, raid starts `minecraft:gameplay/can_start_raid` and the `raids` game rule. - A raid is state of the region that holds its village (#153) and is saved with the dimension; difficulty reaches it as a snapshot (#154). - Builds on Health and combat (#106, #107, #111) and on boss bars (#247) for the raid bar. - Start with #244, which needs nothing; #243 follows goals and damage; #245 needs points of interest (#239). **Done when.** A zombie walks to a player and hits for its attribute damage, a skeleton keeps its distance and shoots, the slime chunk test passes for every recorded seed, and a level-3 ominous bottle drunk in a village on normal starts a raid with six waves.
No due date•0/4 issues closed**Why.** Villages are generated without villagers (#139), and nothing would give a villager a home, a job or trades. Trading halls are the economy of most survival worlds (#182). **Scope.** In: points of interest per section, saved with the world (#239); professions from workstations (#240); trading from the trade data (#241). Later issues here: schedules, gossip, villager breeding, iron golems, zombie villager curing, the wandering trader. Out: placing villagers in generated villages (#139) and raids (#245). **How.** - Lands as removable vanilla plugins over the brain (#230) and pathfinding (#231) of Mob AI, never in the server crate. - Data, not code: which block states are which point of interest is generated by the update program into a dense table by state; trades come from the `villager_trade` and `trade_set` data; activities from the environment attributes. - Work follows change: each section keeps its points of interest, updated when a block is set, and a query visits only the sections in range. - Determinism: scheduler-independent. A claim is exclusive and a contested one goes by a stable key, so two villagers and one lectern give exactly one librarian on every run. Prices are exact arithmetic from base, demand, reputation and discount, compared exactly; the offers rolled for a level are compared by distribution. - Points of interest are saved in the vanilla layout, so a world round-trips through vanilla (#78). - Tests: behaviour tests against the vanilla test server's outcomes (#272), with a mock player (#274) trading. - Start with #239, then #240 and #241. **Done when.** A trading hall from the test pack works, a used-up trade locks and unlocks on restock, and a world saved with points of interest opens in vanilla at the targeted version (`assets/minecraft/version.json`) with them in place.
No due date•0/4 issues closed**Why.** Animals are generated and stand still: nothing breeds, follows food, gives wool or milk, or can be tamed or ridden (#182). **Scope.** In: the farm animal baseline of wandering, following food, breeding and panic (#234); shearing, milking, bucketing and leashing (#235); taming, following, sitting and defending (#236); horses, donkeys and mules (#237). Later issues here: each animal kind, from chickens and eggs, sheep wool, bees and hives, frogs and axolotls to turtles, armadillos, sniffers, goats, foxes, pandas, wolves, cats, parrots, llamas and camels. Out: spawning (Mob spawning) and villagers (Villagers). **How.** - Lands as removable vanilla plugins over Mob AI, never in the server crate. - Determinism: timers are exact and compared tick for tick: love lasts 600 ticks, parents wait 6000, and a child becomes an adult exactly 24000 ticks after birth. Taming chances and inherited traits are compared with the vanilla test server's distribution in the exhaustive tier. - Data, not code: each animal's food is its item tag, drops and shearing come from loot tables, and a bucketed mob's data travels in the bucket's components. - A ridden horse is driven by its rider's client and checked with the shared movement model (#85, #94), like a player. - A tamed animal's owner is stored by id and saved with the entity through the persisted component registry (#159). - Tests: behaviour tests against the vanilla test server's outcomes (#272), with a mock player (#274) feeding, shearing and leashing. - Start with #234 once goals (#229) and damage (#106) land; #235 needs item use on entities; #237 needs the movement check (#94). **Done when.** A cow farm from the test pack breeds as in vanilla, a tamed wolf attacks a zombie that hits its owner, and a mule is born from a horse and a donkey.
No due date•0/5 issues closed**Why.** Mobs stand still. Vanilla drives them with two mechanisms composed in code per mob: prioritised goals for most mobs, and a brain of memories, sensors and activities for villagers, piglins and the newer mobs. Every mob family in later milestones builds on these (#182). **Scope.** In: goal selection (#229); memories, sensors and activities (#230); pathfinding on land, in water and in the air (#231); move, look and jump control (#232). Out: each family's own goals and behaviours, which arrive with Animals, Villagers, Hostile mobs and raids, and Bosses. **How.** - Lands as removable vanilla plugins, never in the server crate. - The ECS shape is settled first (#228). A behaviour is a system over the mobs that carry its component, never an object per mob with a virtual tick; which behaviour runs is a value, so switching moves no entity between archetypes; memories are components with an expiry tick; sensors are systems that run at their interval. - Determinism: scheduler-independent. The same world gives the same path and the same chosen goal, and a conflict between mobs is decided by a stable key such as the entity id. Rolled behaviour is compared with the vanilla test server's distribution in the exhaustive tier; reaching a point at the speed an attribute gives is compared exactly. - Work is bounded: path search has a budget per tick and reuses its buffers, with its cost recorded in `PERF.md`; sensors query the cell-grouped indexes (#98, #99). - Activities switch on the schedule from the environment attributes `minecraft:gameplay/villager_activity` and `minecraft:gameplay/baby_villager_activity`, not on a clock written in code. - Builds on Entity physics (#97, #98, #99) and on attributes (#104). - Start with the decision in #228, then #232 and #229; #231 and #230 follow. **Done when.** A zombie finds and attacks a player around an obstacle, a villager does not walk through a closed iron door, and a fish stays in water, each in a behaviour test against the vanilla fixture.
No due date•0/5 issues closed**Why.** Mobs exist only where world generation placed them. Survival needs monsters at night and animals in fields, and without despawning the cap fills and spawning stops for good (#182). **Scope.** In: natural spawning by category with caps and placement rules (#225); despawning (#226); spawners (#227). Later issues here: trial spawners and vaults, phantoms, patrols, cats, the wandering trader, iron golems summoned by villagers. Out: slime chunks (#244), raids (#245), what mobs do once spawned (Mob AI). **How.** - Lands as removable vanilla plugins, never in the server crate. - Determinism: scheduler-independent. Vanilla's random sequence is not matched, only its distributions: spawn rates, spawner delays and despawn lifetimes are compared with the vanilla test server's bands over many seeds in the exhaustive tier. The gates are exact and tested exactly: no natural spawn within 24 blocks of a player, removal at once beyond 128 blocks, a spawner that stops at six of its type nearby. - Data, not code: spawn lists per position come from the environment attribute `minecraft:gameplay/natural_mob_spawns`, categories and entries from the loaded data; the game rules `spawn_mobs` and `spawner_blocks_work` and the difficulty gate them. - Work follows the players: entity counts come from the cell-grouped indexes of #98 and #99, never a scan, and a dimension with no player runs no checks. - Caps are counted per region (#153), one region per dimension until regions split; difficulty and game rules arrive as a snapshot (#154). - Start with #226 and #227, which need the entity index; #225 also needs the nearest-player index (#99). **Done when.** A dark-room mob farm from the test pack yields within vanilla's band, and natural spawning stops at the monster cap until the other monsters are name-tagged.
No due date•0/4 issues closed**Why.** Sculk sensors, shriekers, the warden, the allay and the frog react to game events, and no game event is ever emitted (#182). **Scope.** In: game events emitted with their source and position (#221); vibrations travelling to listeners with delay and occlusion, with the sculk sensor as the first listener (#223). Later issues here: calibrated sensors, shriekers and the warning level, sculk catalysts and spreading, allay and warden listeners. Out: the warden itself (Bosses) and the vibration particle on the client (#137). **How.** - Lands as removable vanilla plugins, never in the server crate. - Events are typed ids emitted where the reference emits them, never strings. Notification radii are data generated by the update program, not a list in Rust; occlusion, dampening and sneaking come from the tags `#occludes_vibration_signals`, `#dampens_vibrations` and `#ignore_vibrations_sneaking`. - Work follows the listeners in range: listeners register by section, so an event costs the listeners within its radius, never every listener of the world. - Determinism: scheduler-independent. Travel time follows from distance, and when several vibrations reach one listener the reference's selection keeps one, never the first system that ran. Outcomes and ticks are compared with the vanilla test server's fixtures (#272). - Builds on #152 and #160: nothing interacts across the gap between regions, so the distance at which regions join has to cover the largest notification radius. - Start with #221, then #223. **Done when.** A sculk sensor door from the test pack opens on vanilla's tick, a block broken six blocks from a sensor powers it with vanilla's strength, and a wool block between them stops it.
No due date•0/3 issues closed**Why.** Block entities are loaded, saved and sent, but only chests do anything. Hoppers, furnaces, spawners and beacons need a tick; signs, beds, lecterns and banners need interaction (#182). **Scope.** In: ticking only the block entities that have behaviour (#218) and editable signs (#219). Later issues here: cake, beds, lecterns, beehives, banners, skulls, beacons, conduits, jukeboxes, decorated pots, chiseled bookshelves and archaeology. Out: containers (Block containers), furnaces (#211), hoppers (#201), spawners (#227). **How.** - Lands as removable vanilla plugins, never in the server crate. - Work follows change: a block entity whose type has a ticker joins a list when it is created and leaves it when removed, so a chunk full of signs costs nothing per tick. - Determinism: the ticking order is exact, as the reference. Tickers run after entities, in insertion order; one created this tick first runs next tick; one removed during the tick does not tick again; tickers stop with the chunk's ticking level. Interaction is scheduler-independent: two players editing one sign are ordered by arrival. - Sign text and other block entity data reach the viewers of the block as frames encoded once (#155), and are saved with the chunk (#78). - Tests: ticking order by behaviour tests against the vanilla test server's outcomes (#272); interaction by a mock player (#274) and with a vanilla client. Our client draws signs and their text (#134). - Start with #218, which Item transport and Workstations and recipes wait for; #219 needs `open_sign_editor`, `sign_update` and `block_entity_data` typed. **Done when.** A placed hopper ticks from the next tick, an item entering it in the same tick moves before it ticks, and a player writes both sides of a sign that a second player sees and that survives a restart.
No due date•0/3 issues closed**Why.** Enchantments are parsed with all their effect components, but almost none take effect: silk touch zeroes block experience and binding blocks armour removal, and that is all (#182). **Scope.** In: one dispatcher for effect components with their conditions (#214); efficiency, silk touch, fortune and unbreaking when mining (#215); the enchanting table (#216). Later issues here: the damage and protection families, bows, crossbows and tridents, the mace, movement enchantments, mending, curses, anvil merging. Out: fortune's numbers, which live in the loot function `apply_bonus` (#74); enchantability on items (#126). **How.** - Lands as removable vanilla plugins, never in the server crate. - One dispatcher: for each effect component type, a query over the relevant equipment returns the effects whose conditions hold, in the reference's order, and lookup by component type is a dense table. No consumer walks enchantments on its own, and nothing is matched by string. - Determinism: the table's offers come from the player's enchantment seed, saved with the player, so they are exact: the same item and seed give vanilla's offers. Fortune drops, unbreaking wear and other rolled effects are compared with the vanilla test server's distribution in the exhaustive tier. - Attribute effects become modifiers on the entity (#104) and leave with the equipment; an effect that changes movement enters the shared movement model in the same change. - Builds on Loot (#73, #74) for conditions and bonuses, on Health and combat (#104) and Item behaviour (#118), and on #159 for the seed saved with the player. - Start with #214 once loot conditions (#73) and attributes (#104) land; #215 needs durability (#118); #216 needs menu buttons (#212). **Done when.** An enchanted pickaxe from the table mines as in vanilla: efficiency breaks stone in vanilla's number of ticks, silk touch drops the block, and fortune and unbreaking fall within vanilla's distributions.
No due date•0/4 issues closed**Why.** Recipes load as a reloadable registry (#327), but nothing crafts, smelts or reads a menu button, so a survival game cannot get past its first minutes (#182). **Scope.** In: the crafting grid and table (#209); recipes and the recipe book on the client (#210); furnaces, smokers and blast furnaces (#211); menu buttons and renaming (#212). Later issues here: stonecutter, smithing table, anvil, grindstone, loom, cartography table, brewing stand, campfire. Out: the enchanting table (#216), the crafter (Item transport), recipes unlocked by advancements (#257). **How.** - Lands as removable vanilla plugins over the shared click model and the inventory transaction, never in the server crate. - Determinism: scheduler-independent and without randomness, so outcomes are compared exactly: the same grid gives the same result, and a furnace finishes on vanilla's tick. Two players taking from one block are ordered by arrival. - Data, not code: fuel is the item component `minecraft:cooking_fuel`, cooking time is the recipe's, and smokers and blast furnaces accept their own recipe types only. Matching uses an index built at load, never a scan of every recipe on each slot change. - A menu declares its buttons; an unknown button, a click on a menu the player does not have open, or a rename past 50 characters is ignored or refused. - The recipe book is player state saved with the player through the persisted component registry (#159); recipes reach clients again after a reload (#317). - Tests: furnaces by behaviour tests against the vanilla test server's outcomes (#272); crafting by a mock player acting through the production messages (#274). - Start with #209 and #212; #211 needs block entity ticking (#218); #210 needs the per-player recipe set. **Done when.** A player crafts a pickaxe from logs and smelts iron with it on a vanilla client and on ours, and each workstation's behaviour test matches the vanilla fixture.
No due date•0/5 issues closed**Why.** Farms are the reason most survival builds exist, and nothing grows. Growth is the first large mechanic that is random in vanilla, so it is where the distribution class of tests is proved (#182). **Scope.** In: crops, stems and sugar cane on random ticks (#204); bone meal (#205); saplings growing into trees (#206); fire spreading and burning (#207). Later issues here: cactus, bamboo, kelp and vines, cocoa, nether wart, mushrooms, leaf decay, grass and mycelium spread, farmland moisture and trampling, the composter, amethyst, turtle eggs. Out: random ticks themselves (#188), dispensers (#202). **How.** - Lands as removable vanilla plugins, never in the server crate. - Determinism: scheduler-independent, matching vanilla's distributions and not its random sequence. Tests force single random ticks for exact cases (a crop below light 9 never grows) and compare growth frequencies with the vanilla test server's over many seeds in the exhaustive tier. - Growth happens on random ticks only; a scheduled tick forced on a plant grows nothing. - Data, not code: drops come from loot tables; trees are the configured tree features of world generation, placed into the loaded world through the normal block-set path. The reference keeps fire's ignite and burn odds in code and the block definitions do not carry them; their source is the open question in #207, and a hand-written list in Rust is not an answer. - Fire spreads only within `fire_spread_radius_around_player` of a player, so its tests set that rule. - Builds on #154 through the game rules (#184) and random ticks (#188). - Start with #204 once #188 lands; #205 needs item use (#117); #206 needs the tree features writing into a loaded world. **Done when.** A water-flushed wheat farm and a sugar cane farm from the test pack produce within the vanilla test server's distribution, and a sapling grows the same tree the feature places for the same random.
No due date•0/5 issues closed**Why.** Containers keep their contents, but nothing moves items between them. Sorters, auto-smelters, storage systems and most farms are built from hoppers, droppers and dispensers (#182). **Scope.** In: hoppers on an 8-tick cooldown with redstone locking (#201); dispensers and droppers acting by item (#202). Later issues here: the crafter, hopper minecarts, and the hopper faces of furnaces, composters and brewing stands as those blocks arrive. Out: each item's own dispense behaviour, which arrives with that item's issue; container menus (#63). **How.** - Lands as a removable vanilla plugin, never in the server crate. - Determinism: hopper timing is exact and compared tick for tick. After a successful move a hopper waits 8 ticks, or 7 when its source is a hopper that already ticked this game tick, so the order of block entity tickers matters and follows the reference's insertion order (#218). The slot a dispenser picks is random and compared by distribution in the exhaustive tier. - Work follows change: a hopper finds item entities through the area query of #98, never by scanning every entity, and a dispenser finds its item's behaviour in a dense table by item id. - Tests: behaviour tests from the vanilla test server's outcomes (#272); a chest-hopper-chest chain is compared on the tick its last item arrives. - Builds on #98 and #152 (the entity index grouped by cell) and on #153 through scheduled ticks. - Start with #201 once block entity ticking (#218), the power model (#191) and containers (#63) land; #202 needs scheduled ticks (#186). **Done when.** An item sorter and an auto-smelter from the test pack pass, and a chest-hopper-chest chain delivers the last of 64 items on the tick the vanilla fixture records.
No due date•0/3 issues closed**Why.** Water and lava stand where generation left them. Spread, levels and timing are exact in vanilla, and stone generators, water elevators, item streams and crop flushing all depend on them (#182). **Scope.** In: spread on fluid ticks (#197); water and lava forming stone, cobblestone, obsidian and basalt (#198); waterlogged blocks and bubble columns (#199). Later issues here: sponges, ice and frosted ice, snow layers, water evaporating where the environment says so. Out: buckets (Item behaviour), entity movement in fluids (#88), fluids drawn on the client (#26). **How.** - Lands as a removable vanilla fluid plugin over Block simulation, never in the server crate. - Determinism: exact for spread, levels and timing; fluid ticks drain after the block ticks of the same tick (#186). The random parts, lava's delayed spread with probability 3/4 and lava lighting flammable neighbours, are compared by distribution in the exhaustive tier. - Data, not dimension checks: lava's speed and reach switch on the environment attribute `minecraft:gameplay/fast_lava` at the position, which is how the Nether gets fast lava. New sources follow the game rules `water_source_conversion` (on by default) and `lava_source_conversion` (off). Waterloggable blocks are those whose definitions carry a fluid state. - Flow that moves entities and items goes through the shared movement model (#88), so client and server agree. - Tests: behaviour tests from the vanilla test server's outcomes (#272), tick for tick. - Builds on #153 through the fluid tick queue and on #154 for the game rules. - Start with #197 once #186 lands; #198 follows; #199 needs placement (#189) and fluid movement (#88). **Done when.** A cobblestone generator and a bubble elevator from the test pack pass on the vanilla test server's tick, and a water source on a flat floor reaches level 1 at distance 7 after 35 ticks and nothing at 8.
No due date•0/4 issues closed**Why.** There is no redstone, and redstone timing in vanilla is exact tick for tick. Block-based behaviour tests are themselves circuits (a `start` test block powers the circuit, an `accept` block ends it), so this milestone opens most of the test pack for #182. **Scope.** In: strong and weak power through conductors (#191), dust (#192), levers, buttons, pressure plates, torches and lamps (#193), repeaters, comparators and observers (#194), pistons (#195), and the test block's behaviour (#271, tracked in Gametest harness). Later issues here: doors and trapdoors, note blocks and bells, copper bulbs, tripwire, daylight detectors, target blocks, TNT ignited by power. Out: rails (#250), sculk sensors (#223), dispensers and droppers (#202). **How.** - Lands as a removable vanilla redstone plugin over Block simulation (#185, #186, #187), never in the server crate. - Determinism: exact. Outcomes and ticks are compared with the vanilla test server's fixtures (#272), and trace mode compares the box's blocks on every tick. - Data first: conductivity comes from `minecraft:redstone_conductivity` in the block definitions, as a dense table by state. Where the data is wrong the rule is code: the definitions mark chests and hoppers as push-pull, yet a block with a block entity never moves. - Rules every issue keeps: quasi-connectivity (a piston is also powered through the block above it and reacts only when an update reaches it), at most 12 pushed blocks counting those pulled by slime and honey, torch burnout counted per dimension and never per server. - Negative cases are tested too: a piston never moves or duplicates a block entity, and an update chain past the limit never crashes. - A pushed player moves through the shared movement model (#85), so the client predicts the push. - Builds on #153 through the scheduled tick and block event queues. - Start with #191, then #192 and #193; #194 needs scheduled ticks (#186), #195 block events (#187). **Done when.** A clock, an RS latch and a two-by-two piston door from the test pack pass on the same tick as on the vanilla test server, and so does every block-based instance whose circuit uses only these components.
No due date•0/6 issues closed**Why.** A block in mcrs changes only when a player or an explosion sets it: nothing reacts to a neighbour, nothing is scheduled, nothing grows. Redstone, fluids, growth, item transport and every block-based behaviour test stand on the same few mechanisms, so this milestone comes first in #182. **Scope.** In: game rule values per world, changeable while running (#184); neighbour and shape updates with the chain limit (#185); scheduled block and fluid ticks saved with the chunk (#186); block events (#187); random ticks (#188); placement from the placement context (#189). Out: what each block does on these hooks (torches, lamps, crops, falling sand), which belongs to Redstone, Fluids, Growth and Block behaviours. **How.** - The queues are region state; what blocks do with them lands as removable vanilla plugins (#282), never in the server crate. - Determinism: exact, as the reference. In a dimension tick, scheduled block ticks run before fluid ticks, then random ticks, block events, entities and block entities. Scheduled ticks drain by trigger tick, then priority, then scheduling order, at most 65 536 of each per tick. Neighbour updates go W, E, D, U, N, S and shape updates W, E, N, S, D, U, from a queue rather than recursion; past the chain limit (1 000 000 by default) they are dropped with a warning, never a crash. Random ticks are compared by distribution. - Work follows change: a section with no randomly ticking state costs nothing, a queue costs what is due, buffers are reused, and a block's behaviour is found in a dense table by state id, never by a string or map lookup. How a block names its behaviour family is the open decision in #183. - Tests: each mechanism has a behaviour test whose outcome and tick come from the vanilla test server (#272). - Builds on #153 (the region owns the tick queues, block events and the sub-tick counter), #154 (game rules reach dimensions as a read-only snapshot) and #78 (pending ticks are saved with the chunk). - Start with #185 and #189, which need neither; #186 and #187 follow #153, #184 follows #154. **Done when.** A torch whose support is removed is gone in the same tick with one item dropped, and a chunk saved with pending ticks runs them on the same game tick after a load, in mcrs and in vanilla. **Decisions** - #367 Determinism comes in three tiers, exact as the reference, independent of the scheduler, and free - #368 Block state data lives in dense tables indexed by numeric id, and a tag is an ordered list with a derived bit set
No due date•4/11 issues closed**Why.** Vanilla is the authority on behaviour, and its own headless test server runs any test written in its format. A harness that runs the same `test_instance`, `test_environment` and structure `.nbt` entries turns every later gameplay milestone into a comparison with recorded vanilla outcomes instead of with our reading of the code. It serves #182, and with bots it measures load for #181. **Scope.** In: a separate test pack the production server never loads (both test registries are synced to every client), a headless dimension with a fixed seed and a ticket that belongs to no player, structure placement through the generation path, the driver and its outcome tick, one nextest case per instance, environments, the test block, Rust scenarios, a mock player, vanilla outcome fixtures, then parallel batches, handover mid-test and bot load. Out: the mechanics under test (their own milestones) and vanilla's `/test` command and test block screen. **How.** - Its own crate over the engine and the vanilla plugins a pack needs, configured through its plugin: seed, flat preset, pinned grid origin, pack list. No environment variable, no lookup in the working directory. The test block's behaviour lands in the redstone vanilla plugin, not in the harness. - Ticks are stepped exactly, with no wall clock. The reference stops entities and block entities 300 ticks after the last player leaves, so a test holds its box with its own ticket. - Two assertion classes. Exact: the same outcome on the same tick as vanilla, and in trace mode the same blocks on every tick. Distribution: frequencies over many seeds against vanilla's, in the exhaustive tier, with one short case in the default tier. - The harness itself is scheduler-independent: a test gives the same outcome alone, in a batch and on many threads. A running test is an entity whose outcome is truth; scenarios are data whose actions are messages to the production systems, never a closure over `&mut World`. - Start with #265, #266 and #267, which wait for nothing; #272 needs only the pack. Parallel batches wait for #160, handover for #166, bot load for #151. **Done when.** Every block-based instance of the pack passes in mcrs on the tick the vanilla fixture records, each as its own nextest case, and gives that outcome alone, in a parallel batch and across a region handover. **Decisions** - #372 Behaviour tests use the vanilla gametest format, in a test pack outside the production packs - #373 The vanilla headless test server is the oracle, and its outcomes reach our tests only as recorded fixtures - #374 Every behaviour test asserts either exact vanilla ticks or vanilla distributions - #375 Tests fix the seed and the grid origin and run in a deterministic default environment - #376 Behaviour tests drive only production systems, through the messages a real player or block would produce
No due date•5/19 issues closed**Why.** The engine is meant to run with no vanilla behaviour at all: a lobby, a minigame or a test server takes worlds, dimensions, chunk sources, players, events and commands, and pays for nothing else. Today the server plugin adds every part in one list, each dimension world gets a fixed set of gameplay plugins with no way to leave one out, settings come from environment variables and `ops.json` in the working directory, and a plugin that wants to refuse a block break has nowhere to hook in. This milestone serves the library direction (#180). **Scope.** Configuration only through the plugin (#278); the server plugin as a group of removable parts (#279); user plugins inside every dimension world (#280); the chunk source as a plugin point with empty and flat sources (#281); vanilla gameplay out of the server crate (#282); public, cancellable player events (#283); dimension types and a minimal registry set from Rust (#284); a typed command tree with permissions (#285); world registries and embedded packs added by a crate (#286); and the `hello_server`, `lobby` and `arena` examples that prove it (#287). Out: the public way to send an arbitrary packet, which is the encode-once frame of #155; the example harness (#288); the client login (#303). **How.** - What the server does with a player, a chunk, a command or a packet goes through a public message or plugin point a user can observe, replace or cancel. A cancelled action sends the client what undoes its prediction. - No library crate reads an environment variable or a file in the working directory. The binary may translate them into plugin fields. - Vanilla parts talk to the engine only through public messages and components, never through a field of another part, and each one can be removed alone. A member that needs another fails at build naming the missing plugin. - A chunk source yields columns off the tick, on the task pool. Start with #278 and #280, which have no blockers; #279 and #282 can land in either order. Every gameplay issue is blocked by #282. **Done when.** A server built without world generation, lighting and the vanilla plugins lets a vanilla client and our client into an empty world, and the three examples of #287 pass their tests with no world generation, Anvil or light crate in their dependency tree. **Decisions** - #437 No facade or API crate; the public surface is the crates and plugins themselves
No due date•7/13 issues closed**Why.** World generation runs on every core while players explore, so its cost per column bounds how fast a spread-out population can load new land, which is the realistic case for thousands of players in one dimension (#181). Several costs are known from reading the code: every column allocates its scratch space afresh; the biome search evaluates all six climate parameters at every point although five do not depend on height; the surface pass computes rule context the compiled rules never read, so the Nether and the End pay for overworld context; structure templates rotate block states by property name, a string lookup per placed block. And nobody knows how generation scales across cores: the overworld bench runs on one thread. **Scope.** No per-column allocation (#170); overworld generation measured across cores (#171); surface rules that skip unread context (#172); the biome search hoisting the terms constant over a column (#173); template rotation through a state table (#174). Out: start time and registry loading (#55, #313), unported generation features (the worldgen completeness work). **How.** - Every item is the same computation done cheaper: the same random numbers, the same traversal order, the same values at lattice points. A change that moves a single block for a seed is a bug and is reverted. Output stays bit-identical against the existing biome, surface and structure fixtures. - Each item lands with a before and an after figure in `PERF.md`, from the stage bench `crates/mcrs_minecraft_worldgen_generator/benches/overworld_pipeline.rs`. - Judge on a large region (256 by 256 columns), not on 16 by 16, where the gain of a small change is below the spread between two identical runs; and report the spread, since a region average can hide a twofold difference between biomes. - Earlier per-column figures predate the staged pipeline and are not a baseline; re-measure before comparing. - Scratch buffers live per worker and are reused across columns; a column allocates only what it returns. Start with #171, which says which stage stops scaling first, and #170; the others are independent. **Done when.** The overworld bench counts zero allocations per column after warm-up, columns per second at 1, 4, 8 and 16 threads are in `PERF.md` with the stage that stops scaling first, and every item's before and after figures are recorded. **Decisions** - #381 A worldgen optimisation never changes a block for a given seed - #382 A generation speed-up is accepted only on 65,536 columns of land, measured A/B/A - #383 Density functions compile into a flat program filled one node at a time over a whole volume - #384 Worldgen noise has a fast default profile within a stated error budget and a strict profile that matches the reference bit for bit - #385 A column is generated through a ladder of stages over its neighbours, on its own task pool, nearest first - #386 Carver sources are walked once per two by two columns, and the masks are kept in a bounded cache
No due date•6/11 issues closed**Parked** until the bot-load baseline (#151) shows a limit that one node cannot meet. The engine seams this design needs moved to Engine seams for scale. **Why.** The target is ten thousand players in one dimension with the unmodified vanilla client (#181). The binding limit is not the tick but how many players see each other: a moving player costs each viewer roughly 200 to 250 bytes per second before compression, so a thousand in view is about 2 Mbit/s per client. The `transfer` packet and a return to configuration make the client drop its world, so a seamless move between processes needs one process to keep the connection. The shape that meets this: edges hold connections and decide who sees what, simulation nodes own regions, a coordinator owns ownership and global state. **Scope.** The engine part every server gets, free with one node: cells (#152), regions owning the ordered layer (#153), global state as versioned snapshots (#154), frames encoded once and delivered by cell (#155), a visibility budget (#156), wire ids independent of the owning world (#158), one registry of persisted components (#159). Then regions that form and merge (#160) and the opt-in distribution group (#162) with the edge process (#163), several nodes (#164), handover with epochs (#166) and the coordinator (#167). Measurements: #151, #61, #157, #161, #165. Out: chunk saving (#78), the proxy library (#296). **How.** - Clients talk only to an edge; nodes never hold a connection; only the coordinator changes ownership; nodes send each other handovers, never ownership decisions. - A region is the connected set of cells joined by simulation reach and is owned whole by one node. There are no ghosts, because a neighbour update chain, the level-wide sub-tick counter of scheduled ticks and block events cannot be split between owners within one tick. - A cell is 32 chunks by default, one Anvil region file, so its owner is the single writer of that file. - The edge core is tokio and the protocol crates without Bevy, shared with the proxy. Wire ids are never reused and survive a move; every message carrying authority is stamped with its region's epoch. - Only the distribution group serialises messages in process; a server without it pays nothing. Start with #151. The engine steps come early because gameplay is blocked by them: block simulation by #153, game rules by #154, entities by #158, saving and travel by #159. **Done when.** Bots spread over one dimension play across several nodes behind an edge, a handover mid-test gives the same outcome on the same tick, and the figures are in `PERF.md`. **Decisions** - #351 Ten thousand players means spread over one dimension, with a bounded number visible to each client - #352 The edge holds each client connection for the whole session and is the only role a client talks to - #353 A region is atomic, joined by simulation reach, with no ghosts across its boundary - #354 A cell is a square of chunks, 32 by default and configurable, so one owner is the single writer of one Anvil file - #355 Wire entity ids come from leased ranges, are never reused while running and survive a change of owner - #356 One registry of persisted components serves the save, region handover and dimension travel - #357 Global mutable state has one writer, the main app in one process and the coordinator in many - #358 Under load the server degrades visibility first, then far mob ticks, then one region's clock, then admission - #359 Distribution is an opt-in plugin group, and the only place messages are serialised in process - #360 The edge core is a crate without Bevy, shared with the proxy library - #361 The scaling types land before gameplay, in their degenerate form of one process, one node and one region per dimension - #362 Each dimension runs as a sub-app with its own world, fed by extraction from the main app - #377 The main app and each dimension talk only through bounded channels, and the main app never blocks on a send - #378 A player is addressed across worlds by a session id never reused and an epoch that counts the dimensions it has left - #380 Every dimension is spawned when the server starts playing, and dimensions are pumped one after another
No due date•15/26 issues closed**Why.** Moving an entity between dimensions works on the bus side, with confirmation, rollback and timeouts, but the arrival is a stub: the entity is not a full player, the client is not told to respawn, and no state travels with it. Portals exist only as generated blocks (#182). **Scope.** In: players arriving as full players (#142), Nether portals (#143), End portals (#144) and End gateways (#145). Out: the dragon fight that opens new gateways (#248). **How.** - Lands as removable vanilla plugins, never in the server crate; the transfer itself is an engine mechanism that a minigame uses without portals. - A traveller crosses as a snapshot of its persisted components, the same form that saves it and that a region handover moves (#159), so travel, saving and handover cannot drift apart. Wire ids do not depend on the owning world (#158). - Determinism: scheduler-independent and without randomness in the frame and the link, so frame detection, portal linking and the landing position are compared exactly with the vanilla test server's outcome. When several travellers arrive in one tick, their order is a stable key, never which dimension ticked first. - A lit portal breaks when its frame does, through shape updates (#185), and the movement check restarts from the arrival position (#94). - Tests: behaviour tests for frames and travel against the vanilla fixtures (#272), and a session with a vanilla client that changes dimension and keeps its inventory. - Start with #142 once #159 lands; #143 needs #185; #145 waits for #248. **Done when.** A player walks through a Nether portal and back on a vanilla client and on ours with inventory, view and tracking intact, and filling an End portal frame with eyes opens the portal. **Decisions** - #379 A move between dimensions is final only when the target confirms the spawn, and rolls back after 100 ticks
No due date•1/5 issues closed**Why.** Generation matches vanilla for density, surfaces, ores and most features, but gaps remain: End columns get no biomes, jigsaw structures carry no entities, a few inputs place nothing, the world spawn is fixed at (0, 128, 0), and the memo of structure starts grows without bound (#182). **Scope.** In: End biomes and features (#138); entities from jigsaw templates (#139); features and template modifiers that place nothing (#140); parity coverage for carvers, biomes and Nether and End terrain (#141); the world spawn (#175); bounding the structure start memo (#176); grass under carved overhangs (#177); melting icebergs (#178); post-processing marks and face updates (#179). Out: generation speed (Worldgen performance) and structure state lost on reload (#81). **How.** - The work lands in the worldgen crates, which run without the engine. What touches a live world (the spawn, post-processing through shape updates, placed entities) is applied by removable vanilla plugins, never by the server crate. - Determinism: exact. Generation is a function of the seed and the data alone and matches vanilla block for block. - Tests: parity fixtures captured from vanilla at the targeted version (`assets/minecraft/version.json`) by the update tool, never written by hand; a fixture recorded at another version fails a test. - Memory is bounded: a structure start is a pure function of seed and position, so evicting it is always safe, and the memo reports its size. - Builds on shape updates (#185) for post-processing and tree face updates, and on Persistence (#78) for saving the chosen spawn. - Start with the End bug (#138) and the spawn (#175); #179 waits for #185, and #139 is complete only once villagers take professions (#240). **Done when.** Generated columns match vanilla in parity fixtures for the End, carvers, lakes, disks and icebergs, and a new world's spawn matches vanilla's for a set of seeds. **Decisions** - #387 Parity fixtures are dumped from the reference by a tool and replayed in full, including the random generator's state
No due date•2/11 issues closed**Why.** Our client draws terrain, but no edits, entities, particles, sound or weather reach it, and it cannot act on a block. Every gameplay issue promises to work end to end with our client, and these are its foundations (#182). **Scope.** In: block updates (#29); block targeting (#132); breaking, placing and using blocks (#133); block-entity blocks as baked models (#134); entity tracking and drawing (#135); sounds (#136); particles (#137); sky, fog and light blended from biomes (#338); terrain fog, water fog and night vision (#339); the rule on baking sky constants (#340); weather (#341); armour, held items and special item models (#342). Out: what each gameplay feature draws, which arrives with that feature's client part. **How.** - Client only. World state, input and the network live in the headless client; drawing, sound and particles are plugins the client adds, and a bot is the same client with them off (#301). - Rendering rules: environment attributes are evaluated on the CPU once per frame; what is constant within a dimension is baked into the pipelines and what varies goes into one uniform written per frame (#340 decides whether sky values keep that rule); no field is looked up by string during a frame. - A block entity's still part is a baked model meshed with its section, and only moving or data-driven parts are drawn apart, so a field of chests costs what ordinary blocks cost. - Determinism: nothing is simulated here; the client follows the server. - Tests: our client and the vanilla client against a vanilla server at the targeted version (`assets/minecraft/version.json`) show the same result. - Builds on the typed receive path (#57), renderer-owned residency (#18) and client registry sync (#53). - Start with #29 and #132, then #133 and #135. **Done when.** Two of our clients on one server see each other walk, break and place blocks and drop items, with sound, particles, weather and biome-blended sky as in the vanilla client. **Decisions** - #399 Environment attributes are evaluated on the CPU once per frame and reach the GPU as one uniform
No due date•1/13 issues closed**Why.** Our client draws the inventory and receives every container packet, but it cannot click. The server side exists already; this makes our client a full participant, and a bot that manages its inventory is the same client without rendering (#182, #180). **Scope.** In: every single-slot click with local prediction (#129); container screens, tooltips, drag painting and double click (#130); the creative inventory screen (#131). Out: workstation screens, which come with their menus in Workstations and recipes, and any server change. **How.** - Client only, as plugins of the client rather than of the server. Inventory state and prediction live in the headless client; the screens are rendering the client adds, so a bot clicks without them (#301). - Every click is applied locally through the same click model and transactions the server uses, then sent with the state id and hashed stacks; when the server disagrees, its reply replaces the prediction. - Determinism: the click model is a pure function, so the prediction and the server agree exactly; nothing here depends on the system schedule. - Tests: not behaviour tests, since the server side is unchanged. A scripted session against a vanilla server at the targeted version (`assets/minecraft/version.json`) ends with identical inventories on both sides and without a single full resend. - Builds on the typed receive path (#57) and on client registry sync (#53). - Start with #129; #130 and #131 follow. Until our client can use blocks (#133), screens are opened for it from the server side. **Done when.** A scripted session of clicks, drags and double clicks against a vanilla server ends with the same inventories on both sides, on screens that match the vanilla client. **Decisions** - #410 The client predicts clicks with the server's planner and transaction
No due date•1/4 issues closed**Why.** The only use of an item the server knows is placing a block. Food, durability, weapons, armour, bundles, special items and fishing are data components that nothing reads (#182). **Scope.** In: item use with start, hold and release (#117); durability (#118); bundles and crossbows (#119); consumables (#120); weapon and defence components (#121, #122); decoration and special items (#123, #124); profiles on heads (#125); enchantability and stored enchantments (#126); armour trims (#127); pick block (#128); fishing (#264). Out: enchantment effects (Enchantments), block placement (#189), drawing items on the client (#342). **How.** - Lands as removable vanilla plugins, never in the server crate. - Data, not code: a behaviour reads the item's data components, never a list of item ids, and an item's dispense behaviour arrives with that item's issue (#202). - Determinism: scheduler-independent. Use times, cooldowns and wear are fixed and compared exactly; unbreaking, fishing catches and bite times are compared with the vanilla test server's distribution in the exhaustive tier. - Using an item slows the player, so the use state is an input of the shared movement model (#85). - Tests: a mock player acts through the same messages the packet ingest emits (#274), so a test runs the production path rather than a second one; behaviour that lives in the packet handler is checked with a scripted client. - Builds on Health and combat for damage and effects, and on #159 for the player state that items change. - Start with #117, which most of the rest and the mock player wait for; #118 and #120 follow. **Done when.** A player eats, wears down and breaks a tool, fishes and fills a bundle on a vanilla client and on ours, and each behaviour matches the vanilla test server's outcome or distribution. **Decisions** - #406 An item stack is an entity whose data components are Bevy components
No due date•1/14 issues closed**Why.** Nothing can be hurt. Mobs carry a health value that never changes, players have none, and damage types exist only as registry data. Combat, environmental damage, food and experience are what make survival a game (#182). **Scope.** In: damage events, sources and invulnerability frames (#103); attributes and modifiers (#104); the cause of a change (#105); health, knockback and death (#106); status effects (#107, #113); drowning, fire, freezing, suffocation, contact, void and world border damage (#108, #109, #110, #262); projectiles (#111); experience orbs and levels (#112, #114); equipment seen by others (#115); death drops (#116); food and regeneration (#261). Out: weapon and armour components (Item behaviour) and mob attacks (#243). **How.** - Lands as removable vanilla plugins, never in the server crate. - Everything reports through one damage event with a typed source, and the main mutating events carry a typed cause chain, so a kill by primed TNT traces to whoever lit it. - Determinism: scheduler-independent. Damage, regeneration and starvation are fixed arithmetic and compared exactly with the vanilla test server's outcomes: a full player heals every 10 ticks, a starving one is hurt every 80. Rolled outcomes are compared by distribution. - Whatever changes how a player moves (knockback, speed effects and attributes) reaches the client through the ping brackets of #93 and enters the shared movement model in the same change. - The game rules `fall_damage`, `drowning_damage`, `fire_damage`, `freeze_damage`, `keep_inventory` and `natural_health_regeneration`, and the world border, are global state read from a snapshot (#154); health, effects and experience are saved through the persisted component registry (#159). - Start with #103 and #104; #106 follows, then the damage kinds. **Done when.** Players and mobs take damage, are knocked back, die and respawn on a vanilla client and on ours, each damage kind matches the vanilla test server's outcome, and `keep_inventory` decides the death drop.
No due date•0/17 issues closed**Why.** Only dropped items move: gravity, drag and friction live in their own tick. Mobs and players pass through each other, a player's movement is not delivered to others, and every move is sent as a full position (#182). Tracking is also where gameplay meets the scaling architecture (#181). **Scope.** In: the position sync bug (#96); area queries over the per-section index (#98) and a nearest-player index (#99); velocity and gravity for every entity (#97); mounting and dismounting (#100); fall damage (#101); one tracking path with real movement packets (#102); pushing and cramming (#263). Out: player movement (Player movement), damage itself (Health and combat), vehicles (Vehicles). **How.** - Lands as removable vanilla plugins, never in the server crate. - Determinism: one integration step for every entity with a velocity, using the collision of the player movement model, so its arithmetic is exact and compared with vanilla's. Pushing is scheduler-independent: each entity writes only itself and a pair is decided by entity id. Cramming damage is compared by distribution. - Work follows change: a resting entity costs nothing, a resting crowd costs only its overlap checks, and neighbours come from the area query, never a scan of every entity. - The entity index is grouped by cell (#152), so a region owns the entries of its cells and the edge reads the same index. The simulation publishes what changed per cell; the edge chooses recipients by subscription and visibility budget (#155, #156); wire ids do not depend on the owning world (#158). The tracking range comes from the entity type. - Tests: behaviour tests against the vanilla test server's outcomes (#272), and a delivery test with two connected clients. - Start with #96, before #155 replaces addressing by session; then #98 and #97. #99 is built only with its second consumer, the visibility budget, and measured before its structure is chosen. **Done when.** Two of our clients see each other move, 25 chickens in a one-block hole begin to die while 24 never do over 1200 ticks, and fall damage follows the verified fall rather than the client's ground flag.
No due date•1/9 issues closed**Why.** The server accepts any position a client reports, and our client's player only flies. Both need vanilla's movement rules, written once as a pure function: the client moves with them and the server checks against them, as the shared click model serves inventory on both sides (#182). A movement crate without Bevy also serves bots and proxies (#180). **Scope.** In: the shared model (#85) with block collision (#86), walking, jumping and block effects (#87), fluids (#88), step-up (#89), pose and eye height (#90) and elytra flight (#91); the client moving with it (#92); knowing what the client had applied (#93); checking movement (#94) and reach (#95); the teleport bugs (#258, #259, #260). Out: vehicles (Vehicles), motion of mobs and items (#97), knockback and effects themselves (Health and combat). **How.** - The model is a crate without Bevy that advances a player one tick from a state, the input and a view of the blocks around. The server's check lands as a vanilla plugin and is how that plugin handles movement, not an optional layer; the teleport handshake belongs to the player session. Whatever changes movement (effects, enchantments, pistons, fluids) is added to the model in the same change as its cause. - Determinism: exact. The model follows vanilla's arithmetic at the targeted version (`assets/minecraft/version.json`), since any difference becomes a false setback. - Tests: movement recorded from vanilla clients replays through the model tick by tick with no difference. The server brackets every change that affects movement with a ping and checks each movement against the world as the client had applied it. Until the recorded sessions pass, mismatches are measured and logged, not set back. - Builds on #154 for the world border and game rules, and on #158 for entity ids that do not depend on the owning world. - Start with the bugs #258 and #259, then #85 and #86. **Done when.** Recorded vanilla sessions pass with no setback, while flying, speeding, walking through blocks and ignoring knockback are set back.
No due date•3/14 issues closed**Why.** Login is offline only, and its client side is a private function inside the Bevy client plugin, so a bot, a load test or a proxy cannot join a server without the engine. The server never enables compression, although chunks are most of the traffic and vanilla compresses at a threshold of 256. It accepts any protocol version, drops transferred connections without a word, answers the server list ping from constants, holds a login slot forever when a client stops answering, and keeps a rate-limit bucket for every address that ever connected. This milestone serves the library direction (#180). **Scope.** Online mode with encryption and session authentication on both sides (#82); refusing a different protocol version (#83); real player counts and the configured description in the status response (#84); a public client login without the engine (#303); compression at login (#304); a login deadline and bounded per-address buckets (#305); accepting or refusing transferred connections (#306). Out: Microsoft account sign-in (#294), proxy forwarding (#296), moving the Bevy plugins out of the network crate (#60). **How.** - Login lives in the network layer: tokio, no Bevy. The client side is one public async function from a stream and a profile to a connection in the game state with the registry data it received; the Bevy client plugin calls the same function. - The cipher (AES/CFB8) sits on the raw byte stream below the length prefix and is switched on between two packets, when the following bytes may already be buffered, so it is part of the frame codec's state. Session checks run off the main thread. - Every setting is a field of the server plugin (#278) with the vanilla default: compression threshold 256 and a negative value to disable it, a login deadline of 600 ticks, transfers refused unless enabled. The session service address is a field too, so a test points it at a fake. - Refusals use vanilla's messages; the status names the version from `assets/minecraft/version.json`. Start with the offline half of #303, which needs nothing else; #304, #305 and #83 are independent. The online half of #303 follows #82. **Done when.** A vanilla client and our client join with compression on, in offline mode and in online mode against a fake session server, a test without an `App` does the same through the public login, and the status ping reports the running server's players and description. **Decisions** - #435 MCRS identifies itself as github.com/andreypfau/mcrs with the build's commit - #436 A dedicated server announces itself on the local network by default
No due date•8/9 issues closed**Why.** The server reads an existing world and saves players, but never writes a chunk: every block edit and every change to a container is lost on restart. Nothing handles a termination signal, so stopping the process loses up to 6000 ticks of player progress. The reader of a saved world keeps every region file it opened, whole, in memory until the server stops. An unreadable chunk is logged and generated anew, which would overwrite recoverable data once chunks are written. Structure starts and state such as a mineshaft spawner's mob are not kept at all. This milestone serves the architecture direction (#181): the owner of a cell is the single writer of its file, and the snapshot a chunk saves is the one a region handover carries. **Scope.** Saving changed chunks back to region files (#78); quarantining unreadable chunks (#79); keeping structure state (#81); releasing region files read from a save (#168); a clean shutdown that saves players and the world (#169). Out: the registry of persisted components (#159) and cells (#152), which saving builds on; game rule values per world. **How.** - What a chunk saves comes from the registry of persisted components, the same one that carries a player between dimensions and hands a region over. A region file is written by the owner of its cell. - Writes run off the main thread, on unload and at intervals; player autosave moves off the tick the same way. - Rewriting a chunk the game wrote keeps what we do not model yet: structures, block and fluid ticks, post-processing and blending data. Before the first writer, decide whether an unknown key is kept verbatim or is a loud error, and test it. - There are no datafixers: a `DataVersion` mismatch is a loud error. - Shutdown follows vanilla's order: stop accepting connections, disconnect players with the shutdown reason, save players, save changed chunks, wait for the writes, exit. The library installs no signal handler; the binary turns SIGINT and SIGTERM into the shutdown message, and a second signal exits at once. - Every retained buffer has an owner, a bound and a release condition. #168 and #169 are independent and can start now. #79 lands before #78, so nothing unreadable is overwritten. **Done when.** A world edited on this server, stopped by a signal and started again, opens in the targeted version (`assets/minecraft/version.json`) with its edits and structures in place, and the reader's reported size returns to zero when no column is wanted. **Decisions** - #388 Saves are read at exactly the targeted data version, with no datafixers, and an unknown field is a loud error
No due date•1/6 issues closed**Why.** Breaking a block drops the first alternative of its loot table with a count of one: no rolls, no weights, almost no conditions, no functions. Mobs, chests, fishing and archaeology drop nothing. Every later milestone that drops something rolls a table (#182), and a tool or a proxy should roll one without the engine (#180). **Scope.** In: the evaluator with rolls, weights and number providers (#72); every entry type (#71); every condition (#73); every function (#74); loot contexts beyond block breaking (#70); container loot on first open (#76); block drops in full, explosions included (#77). Loading every table, predicate and item modifier is done (#75). Out: the sources of new contexts (killing #106, fishing #264, shearing #235), which bring their context with them. **How.** - The evaluator is a pure function of the table and the context in `mcrs_minecraft_loot`, without Bevy, below the engine. Using it when a block breaks or a chest opens lands as a removable vanilla plugin, never in the server crate. - Determinism: a generated container rolls from the seed stored with it in the save, so that roll is exact and matches vanilla's contents. Every other roll matches vanilla's distribution, not its random sequence, and is compared with the vanilla test server's frequencies over many seeds in the exhaustive tier, with one forced case in the default tier. - Validated at load: every entry, condition and function type is read typed and strict, NBT paths in functions are parsed at load, and an unknown type fails the load. - A drop is a whole stack with its components, so a shulker box keeps its contents (#67). - Start with #72, then #71, #73 and #74 inside its evaluator; #70, #76 and #77 follow. **Done when.** Block, chest, entity and fishing tables drop within vanilla's distributions, a generated chest fills on first open from its stored seed, and blocks destroyed by an explosion drop through the same path.
No due date•2/9 issues closed**Why.** Only a chest that came from world generation or a save can be opened. A placed chest gets no storage, the other container blocks never open, and breaking a container neither spills nor removes what it held (#182). **Scope.** In: container data from block data (#64); block entities that follow their block (#65); every single-block menu, including the ender chest (#66); shulker boxes keeping their contents (#67); lids, sounds, shared viewing and double chests (#68). Out: moving items between containers (Item transport), workstation menus (Workstations and recipes), the client screens (#130). **How.** - Lands as a removable vanilla plugin, never in the server crate. - Data, not code: the block data names each block's block entity type, and slot count, menu type, title and lid sounds follow from it through the generated `MenuType` and `BlockEntityType`; no menu id constant stays in the server. - Every stack change goes through one writer, the inventory transaction, and through the shared click model the client predicts with. - Work follows change: a block entity is found by position through an index, never by scanning every container. - Determinism: scheduler-independent. Two viewers acting on one container are ordered by arrival and both see the result within the tick; a broken container closes every viewer's menu in that tick. - Container contents are saved with the chunk (#78); the ender chest's storage is player state saved with the player (#159). - Tests: behaviour tests with a mock player (#274) against the vanilla test server's outcomes, and sessions with a vanilla client and with ours. - Start with #64, then #65; #67 waits for the loot functions (#74). **Done when.** A vanilla client and our own client can each place every generic container, fill it, share it with a second player, break it and get the contents back. **Decisions** - #405 Transaction is the only writer of stack truth - #407 Slot rules live once in the shared menu snapshot - #408 Slot rule inputs come from enchantment effects, equippable data and tags, never from item ids - #409 A refused click resyncs through the per-slot delta path, never through a full resend - #411 A container's viewer count is derived from the open menus, never stored
No due date•5/12 issues closed**Why.** The codec traits and the framing live in the same crate as the packets of the targeted version, so anything that needs only the codec, such as the core of a proxy, pulls in the whole game vocabulary. The network crate holds the Bevy plugins of the server and the client beside the transport, and both return `anyhow` errors a caller cannot match on. The transport also loses data: past 256 queued packets a connection sheds normal-priority packets, which carry block updates, chat and slot contents (#308); packets held while a session joins a dimension are dropped silently when the channel is full (#307); and the WebTransport endpoint always opens a UDP port and stops accepting browsers once its 14-day certificate expires (#309). This milestone serves the library direction (#180). **Scope.** The wire codec in its own crate (#59), the transport crate without Bevy (#60), and the three transport faults above. Out: encoding broadcast frames once (#155), the proxy library (#296), the profile of the send path under load (#61). **How.** - The wire crate depends only on a byte buffer crate and has its own error type. The derive macros point at it. - Vocabulary crates implement the codec traits for their own types, because the protocol crate no longer owns the trait; the wire forms of item components move next to their types as derives; `glam` replaces `bevy_math` in the protocol. - The network crate keeps connections, packet I/O, the handshake and WebTransport; the Bevy plugins move into the server and the client. - What may be shed is stated per kind of packet: a packet that carries state may be superseded by a newer one for the same target but never just dropped, an event such as a sound may be dropped only by an explicit policy, and a client that cannot keep up is disconnected, as vanilla does. #155 follows the same rule. #59 comes after #54, which removes the codec traits that take a registry context. #60 does not run alongside #28, since both change how the client app is assembled. The three faults are independent. **Done when.** The wire crate's tree holds only the byte buffer crate, the network crate builds without Bevy and returns typed errors, a slow client under a burst of block updates ends with the server's view or disconnected, and a server without the WebTransport option opens no UDP port.
No due date•3/5 issues closed**Why.** The worldgen registries (noises, density functions, surface rules, features, template pools, processor lists, noise settings, structures, structure sets, carvers) still load through Bevy assets, with hand-written walkers that collect references and prototypes that store entry names as strings. A failed asset counts as settled, so a broken load can still start the server. Built-in entries are built as typed values, encoded to JSON and parsed back; density functions are built a whole folder at a time to answer for one entry, 44 to 59 ms to serve all 65 one by one; template pools are scanned linearly on every read. And no check can see two registries at once, so a biome whose placed features reach outside the area a feature may cover loads and fails later. This milestone serves the library direction (#180) and the start time of every server. **Scope.** Built-in worldgen entries as typed values (#313); validators that see the whole set, starting with the biome feature range check (#314); the worldgen registries on the loader (#55). Delivered here earlier: carver sources enumerated once per region of columns (#62). Out: generation speed (Worldgen performance), unported worldgen behaviour (the worldgen completeness work). **How.** - References between entries are checked by serde in registry context; prototypes store ids; one `Holder` type remains, and no name-keyed table. - Worldgen registries load once, in the main app; no dimension world registers a worldgen loader. Structure templates stay assets. - The loading states and `AppState` go, and the generation steps become pure functions of the generator. - A file that fails to parse or names a missing entry stops the load with the grouped report; a file that parses but uses unimplemented behaviour is recorded and the server starts. - Where the order of a loop feeds a random generator or a write order, the order is stated by id and a determinism test surrounds the change. The worldgen parity tests must not gain a failure. Start with #313 and #314, which #55 builds on, and write the list of affected types before #55. **Done when.** Every shipped worldgen file loads, round-trips and is checked across registries; no non-test code of the built-in crate uses `serde_json`; generated terrain is unchanged against the parity fixtures; and server start time is compared with the baseline recorded in `10fed4556`. **Decisions** - #431 The game's built-in worldgen entries stay Rust code and enter the load as typed built entries
No due date•4/5 issues closed**Why.** The server writes the list of synced registries and the list of registries with tags by hand, sorts them by name instead of vanilla's order, and sends a registry vanilla does not sync. The client keeps received registries as raw NBT and parses it at each use. Item components store entry names while the wire carries ids, so a hand-written second family of codec traits takes a registry lookup and costs two hash lookups per encode. Several vanilla types are defined twice. And the world registries are declared in a crate that needs Bevy, so a bot or a datapack checker cannot load them without the engine. This milestone serves the library direction (#180). **Scope.** Loading the world registry set without Bevy (#310); server-side sync from the loaded registries (#52); client-side sync into typed registries (#53); wire codecs for ids without context traits (#54); one Rust type per vanilla record and enum (#311); and a final audit (#312). Out: worldgen registries (#55), reload (#316, #317), the typed receive path (#57). **How.** - A registry is declared once, below the engine: key, value type, validators, the rule for being empty, and its network form. The server's load and the client's rebuild read the same declarations. - The NBT a client receives is a column prepared once at load, not per connection, with one owner and no second network value type. The tags packet is derived from the registered tags. - The client builds its set with a pure function without Bevy, in the order the server sent, with the local corpus only filling entries of known packs. The set is session data replaced whole when configuration is entered again, never patched; a parse error drops the connection. - `Id<R>` encodes as a VarInt without context; decoding checks it against the registry in context, and an id outside it drops the connection. Fields vanilla stores as checked references become ids; fields it stores as bare keys stay keys. - A vanilla type has one definition in the lowest crate that names it; a second type is allowed only as a borrowing view with `From`. Order: #52, #310, #53, #54, then #311; #312 last. #54 lands before #57. **Done when.** A vanilla client joins and receives the 32 synced registries with the known-packs exchange on and off; our client joins our server and a vanilla server with world height, biome colours and the inventory read from typed resources; no context-taking codec trait remains; and the audit finds nothing left.
No due date•4/6 issues closed**Why.** Datapack registries were loaded as Bevy assets one type at a time, addressed by handles, sorted differently from vanilla, and a broken reference showed up when something used it, if ever. Each dimension world had its own asset plugin, so an asset id meant different entries in different worlds and lookups went through the entry name. This milestone puts the world layer on one loader with typed ids. It serves the library direction (#180). **Scope.** Delivered or landing with the current branch: the loader and the simple registries (#49); biomes, dimension types, timelines, world clocks and block state providers on it (#50); typed tags and holder sets (#51); recipes as a reloadable second stage (#327); game rules from the game's data dump (#328); the server's dimension list from its plugin (#329). Open: later packs overriding earlier ones (#315). Out: sending registries to clients (Registry sync), worldgen registries (#55), reload while running (#316), dimension types from Rust (#284), registries added by a crate (#286). **How.** - The loader runs a name phase (list the files of each registry and sort them by vanilla's rule: path first, then namespace, with the `.json` suffix in the compared string) and then a value phase that parses every file with every registry in context. Errors from all registries and entries come out in one report, and nothing loads if anything failed. - Adding a registry is one declaration; its directories follow from its key. - Tags are a column: a tag name maps to a bit set over `Id<R>`. A tag named in a value and missing from the data refuses the load; optional and malformed tag files follow vanilla's rules. - Registries are immutable after the load and reach dimension worlds as cheap `Arc` clones, so an id means the same entry in every world. Saves hold names, never numeric ids. #315 needs a decision before any code: the vanilla rule (the last pack wins for an entry, a tag with `replace` or a file, and the load report names the pack each override came from), or refusing duplicates on purpose as now. **Done when.** The current branch merges, and #315 is decided and, if the vanilla rule is chosen, a pack that changes a vanilla biome loads and the server uses its value. **Decisions** - #423 World registries load in one pure all-or-nothing function over in-memory packs - #424 Registries load synchronously when the world plugin finishes, with no loading state - #425 A broken tag is dropped as the game drops it, but a tag a value names must exist - #426 A tag is an ordered member list with a derived bit set, stored in its registry - #428 Dimension worlds share the host's registries through one Arc - #429 No repeated path looks up a registry entry or tag by name - #432 A dimension is identified by its key, and its type by the id its entry names
No due date•19/19 issues closed**Why.** Tint takes one biome per column, from the topmost section, although at the targeted version a section's biomes are stored per block and change with height. The colours live in a world-space texture that must be wider than everything resident: two columns a window apart share one square of it and one takes the other's colours, and the web build fixes that window at 512 blocks, which caps its render distance. The side of a grass block is a cube face plus a separate tinted quad drawn with an alpha test on every exposed side. This milestone serves the library direction (#180): tint becomes part of the mesh, computed from block and biome data alone. **Scope.** Biome lookup by block position in the mesher (#30); the biome colour table beside `Tint` (#31); a per-section tint palette (#32); records regrouped around a 16-bit tint index (#33); the palette on the GPU (#34); the world-space texture removed (#35); rebuilds on biome changes and a blend radius option (#36); tinted overlays folded into one sprite (#37). Out: the colours sent by a server's registries, which come from client sync (#53). **How.** - Tint stays a property of albedo, multiplied in on the geometry pass. - The mesher computes the colour of each tinted block: the mean of the biome colours over a square window around the block at its own height. The worst case, where every block of a section has its own biome, must work; the common case of whole regions in one biome is the fast one. - The distinct colours of a section form a palette; entry 0 is white and means no tint, and fixed colours are ordinary entries. A palette is usually 8 to 20 bytes per section, and the 4 MiB texture goes. Neither record grows. - Rejected: a texture per column (it cannot vary with height), a dense volume (100 MB for a 512 by 512 by 384 window at one byte per block), and blending biome indices per fragment (25 samples a pixel at radius 2 and 225 at radius 7 for a value constant per block). - The blend radius is a plugin field from 0 to 7, default 2. Record bytes per section, the share of model quads, frame time and the meshing time of a section on a biome border before #30, and compare after #36. The steps run in order from #30 to #36; #37 is independent. **Done when.** The tint texture is gone, biomes that change with height tint correctly, the grass block leaves no extra quad beside its cube, and the recorded figures are compared. **Decisions** - #400 Biome tint is a per-section colour palette computed by the mesher and applied to albedo
No due date•1/9 issues closed**Why.** A raw packet event is raised in four places, and every observer runs on every packet, checks the connection state itself and tries packet types one after another. A broken packet is logged and ignored, an unknown id goes unnoticed, and frames that follow `configuration_acknowledged` in play are checked against the wrong table. Vanilla drops the connection in each of these cases. The public player events (#283) need typed packets to be raised from, and registry ids need one place where the registry context is set so that an id outside its registry is refused while decoding. This milestone serves the library direction (#180). **Scope.** One issue, #57: a typed received-packet event per packet type, one dispatcher, one `ConnectionState`, a rule for bundles, and the id check in `PacketIo::recv_packet`. Out: the packet tables and the frame codec it builds on (done, #56 and #58); the typed packets still missing for cookies and transfer (#295); the wire crate (#59). **How.** - One dispatcher picks the table by connection state once and raises the typed event. Observers stay observers rather than buffered messages per type, because the order of different packet types within one tick matters: vanilla handles them in arrival order. - An unknown id, a packet in the wrong state, a decode error or trailing bytes drops the connection. A row the table knows but has no structure for yet is dropped with a trace, not a disconnect. - One place in the path sets the registry context for decoding; once ids are checked at decode (#54), an id outside its registry drops the connection like any broken packet. - Open decision, to settle first: decode lazily in the handler, which avoids copies but surfaces errors there and never checks a packet nobody observes; or once in the dispatcher, which copies strings and byte fields but checks every packet and sets the context in one spot. This lands after #54, and not at the same time as #53, since both rewrite how the client handles configuration packets. **Done when.** Every serverbound and clientbound packet reaches its handler through a typed event; a test that sends a broken packet and one that sends a game packet during configuration both see the connection dropped; and frames after `configuration_acknowledged` are checked against the configuration table.
No due date•1/1 issues closed