Prior art: how other servers and engines turn Minecraft into data plus components
Researched 2026-10-24 against the source of 24 projects at pinned commits (shallow clones under /tmp/bds-research-priorart/, read only, nothing built) and against public documentation for Mojang's Java server and the Bedrock Script API. This note continues Getting vanilla Bedrock data from Mojang's own files; section 6 there profiles Allay, Dragonfly, PowerNukkitX, Endstone and LeviLamina at a high level and is not repeated here. Every claim names a file and line or a URL. Nothing here is legal advice.
Summary
- Every project separates identity data from behavior code. The vanilla palette (names, states, hashes) is always data, loaded from a dump or a generator; what a block does is always code. The projects differ only in how they attach that code: a subclass per block (Nukkit, PowerNukkitX, cuberite, this server), a behavior type per family bound to many definitions (Mojang Java, Pumpkin, Dragonfly, Minestom), or a component list per definition (Allay, CloudburstMC/Server, Mojang Bedrock itself).
- Pumpkin is the only reimplementation that evaluates Mojang's loot, recipe and worldgen JSON directly. Its density functions are compiled from the datapack at codegen time, its loot evaluator handles about 30 condition and function types, and it checks generated chunks against 29 vanilla chunk dumps block by block. That works because Java Edition publishes all of it; Bedrock publishes none of the terrain.
- Allay's component system is the closest Bedrock precedent: a block type is a list of component singletons wired by reflection, with one base component supplying the lifecycle hooks through direct calls and extra components reacting to ten block events on a per-object event bus. 1477 block types are covered by 366 behavior classes and 173 base component implementations; 1035 are assigned by hand and the rest by a generated null-guarded initializer.
- CloudburstMC/Server has quietly moved to the same shape with less machinery: 65
ComponentTypekeys such asUSE,GET_LOOTandON_NEIGHBOUR_CHANGEDbound to stateless lambdas per block type, with hardness, friction, light and flammability read per state fromdata/blocks.json. - Dragonfly shows the cost side of interface-per-behavior in a language without reflection: 44 block interfaces, roughly 50 type assertions in
player.goalone, and a registryFinalizethat flattens the hot properties into auint16per runtime id so the tick loop never asserts. - Mojang's own extension points define the hook vocabulary a Bedrock server should expose: 15
BlockCustomComponenthooks, 7ItemCustomComponenthooks, dispatch "in the order they are listed", and "a component on a permutation overrides the component on the base block". Entities extend throughtriggerEventand entity properties, not script hooks. - No Bedrock server loads behavior packs server-side. Allay, PowerNukkitX and Dragonfly forward packs to clients. Pumpkin loads Java datapacks from
<world>/datapackswith embedded content first and disk overriding by id, and readspack_formatwithout validating it. - The PrismarineJS stack is a working TypeScript precedent for data-first objects (a
Blockis a record copied fromminecraft-dataplus functions) and for booting BDS in CI (bedrock-protocolboots 19 BDS versions per CI run throughminecraft-bedrock-server). Its Bedrock game data stops at 1.26.30, and that file'sharvestToolsanddropsids resolve to nothing in its own 1.26.30 item table. - The recommendation is pattern (c), definitions plus component lists keyed by Mojang's component names, with Allay's split between one base behavior per archetype and additional components, Dragonfly's load-time flattening for hot paths, and explicit hook interfaces instead of reflection. Reusable under MIT: deepslate's registry and holder shape, Dragonfly's hashing and registration checks,
minecraft-bedrock-serverfor CI, Blockception and bridge. schemas for pack validation. Allay, Pumpkin, PowerNukkitX and Cloudburst are copyleft and serve as references only.
1. Project profiles
Each profile: language and license, commit read, how vanilla data enters, representation, behavior attachment, loot and recipes and worldgen, pack loading, parity checks, plugin API.
1.1 Pumpkin (Pumpkin-MC/Pumpkin)
Rust, GPL-3.0, cd9e98c966919541fe951d78dfdf8d3401e10112, targets Java 26.3. Crates live under crates/.
- Data enters two ways. Extractor JSON is committed in
assets/(68 MB:blocks.json7.7 MB,items.json,entities.json,properties.json, ...), produced by a separate Fabric mod (AGENTS.md:54).assets/NOTICE.md:7-11marks it "(c) Mojang Studios / Microsoft Corporation ... provided solely for compatibility, server emulation, and interoperability under the terms of the Minecraft EULA" and adds "Vanilla Minecraft data packs and structures are not distributed in this repository; they are downloaded on demand at build and codegen time".crates/pumpkin-data/build.rs:5-6pinsMC_VERSION = "26.3"andMANIFEST_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", downloads the server jar and copies itsdata/folder into the gitignoredassets/datapack/. - Code generation is a separate tool,
tools/pumpkin-codegen/(about 70 generator modules,main.rs:27-105), writing committed Rust intocrates/pumpkin-data/src/generated/: 88 files, 1,545,523 lines, 87 MB (structure_template.rs25 MB,block.rs24 MB,biome.rs14 MB,loot_table.rs2.5 MB,noise_router.rs1.8 MB). - Blocks are an immutable generated definition plus an optional behavior object.
crates/pumpkin-data/src/blocks.rs:19-45definesBlock { id, name, hardness, blast_resistance, map_color, slipperiness, velocity_multiplier, jump_velocity_multiplier, item_id, default_state, states, flammable, experience, loot_table };generated/block.rshas 1,286 constants and 116 typed property structs behind aBlockPropertiestrait (to_index,from_state_id,to_props,from_props, lines 2257-2277). Behavior is theBlockBehaviourtrait incrates/pumpkin-core/src/block/mod.rs:73-210with about 35 default-implemented hooks (normal_use,use_with_item,on_place,placed,broken,random_tick,on_scheduled_tick,on_neighbor_update,get_state_for_neighbor_update,emits_redstone_power,get_comparator_output,on_entity_collision,explode, ...). A struct is bound to ids by#[pumpkin_block("minecraft:...")]or to a whole tag by#[pumpkin_block_from_tag("minecraft:doors")](crates/pumpkin-macros/src/lib.rs:352-416).BlockRegistry(block/registry.rs:513-517) is a dense[u16; BlockId::COUNT]index intoVec<Arc<dyn BlockBehaviour>>;default_registry()makes 217registercalls.block/blocks/is 217 files and 33,425 lines: 124 single-block attributes plus 32 tag-wide ones cover 1,286 blocks; the rest getNoneand default handling. - Items: 1,658 generated
Itemconstants, each with a staticcomponents: &[(DataComponent, &dyn Impl)]list (generated/item.rs:73225-73260), plus anItemBehaviourtrait and 56 behavior files. Entities: 161 generatedEntityTypeconstants with attributes, dimensions and spawn rules;entity/is 326 files and 93,855 lines,entity/ai/126 files and 22,513 lines with about 57 goals and a brain port.CONTRIBUTING.md:94-104requires "Mob AI uses goals, selectors, navigation and movement controls, or the Brain's sensors, memories and behaviors". - Loot: one parser,
crates/pumpkin-util/src/loot_table/parser.rs, coversall_of,any_of,alternatives,apply_bonus,furnace_smelt,killed_by_player,looting_enchant,match_tool,random_chance,set_count,survives_explosion,table_bonusand others; unknown types returnNone. Codegen emits 1,450LootTableliterals and the evaluatorcrates/pumpkin-core/src/world/loot.rs(501 lines;check_condition199-277,generate_loot_with_context321,fill_chest_inventory443) accepts either a generated table or one loaded from disk (LootTableHandle, 350-358). Recipes: 523 generated literals plus a runtimedata/datapack/recipe_loader.rs. - Worldgen:
crates/pumpkin-world/src/generation/is 232 files and 62,799 lines. Density functions are not hand-ported:tools/pumpkin-codegen/src/noise_router.rs:2951-2974readsworldgen/noise_settingsandworldgen/density_functionfrom the datapack andparse_vanilla_df(2244-2810) panics on an unknown type.crates/pumpkin-world/AGENTS.md:11-13: "Don't copy numbers from decompiled code when the datapack defines them." - Pack loading:
crates/pumpkin-core/src/data/datapack/(5,876 lines).load_all(mod.rs:119-175) scans<world>/datapacks;scan_datapacks_dir(1255-1292) loads only directories named inlevel.datdata_packs.enabled. Embedded content loads first "so on-disk packs can override them by id" (mod.rs:138).read_pack_mcmeta(1566-1582) readspack_formatwithunwrap_or(61)and never rejects. - Parity: section 4.
- Plugins: Wasm (
pumpkin-plugin-api, MIT OR Apache-2.0) and native. Neither can register a block or item behavior;BlockRegistry::registerneeds&mut selfand the server holds anArc<BlockRegistry>built once (server/mod.rs:97,181). Plugins subscribe to 44 block events instead. - Rationale:
AGENTS.md"Vanilla parity" ("Vanilla defines observable behavior and outcomes, not a required internal algorithm") andCONTRIBUTING.md:79"No Hardcoded Vanilla Values". - Bedrock side note:
assets/bedrock/README.mdlistsblock_states.nbtfrom CloudburstMC/Data andblocks.nbtandbiomes.jsonfrom GeyserMC mappings, for its Bedrock translation layer.
1.2 Minestom and MinestomDataGenerator
Java, Apache-2.0 (generator MIT), 704be783b63cfb0666f38f527f90f5d118cffae3 and 1d9d47235737f5eb406600776cbbe24c80063491.
- Data is a Maven artifact,
net.minestom:dataversion26.2-rv3(gradle/libs.versions.toml:6,50). The generator runs Mojang's own data generator inside a Loom server-only project:DataGenerator.java:24-37callsBootstrap.bootStrap()thenMain.main(new String[]{"--all", "--output=" + tempDir})and converts the output into about 70 JSON files (DataGenType.java): static registries by reflection overBuiltInRegistries, dynamic registries copied verbatim fromdata/minecraft/<name>/, and loot tables with the comment "only included for legacy reasons, Minestom doesn't use them". - Runtime loading:
RegistryData.loadRegistryFile(src/main/java/net/minestom/server/registry/RegistryData.java:121-133) tries the classpath resource, then a same-named file in the working directory. That is the whole override mechanism. - Blocks:
Blockis a sealed interface whose Javadoc says "Implementations are expected to be immutable" (instance/block/Block.java:35-42);BlockImplis a record of the registry entry, alongof properties packed at 5 bits per value, optional NBT and a nullableBlockHandler(BlockImpl.java:27-36).BlockHandler(BlockHandler.java:22-87) hasonPlace,onDestroy,onInteract,onTouch,tick,isTickable.BlockManagermaps handler keys to suppliers and substitutesBlockHandler.Dummywith a warning when a saved chunk names an unknown handler (BlockManager.java:41-52). No vanilla placement rules, redstone or ticking exist. - Items:
Material.prototype()returns aDataComponentMap;ItemStackis immutable withwith(DataComponent, value);DataComponents.javadeclares 111 components. Entities: sealedEntityTypeplus a class hierarchy (300 files);entity/ai/is 17 files and 1,317 lines with seven generic goals. No loot evaluator, no recipe JSON,Generatoris a callback (instance/generator/Generator.java:6-12). - Dynamic registries: 64 accessors on
Registries.java;DynamicRegistry.register(Key, T, DataPack)andremove(DynamicRegistry.java:122,140), frozen after startup unlessREGISTRY_UNSAFE_OPS. - Plugins are the host program:
MinecraftServer.getBlockManager().registerHandler(key, supplier)andblock.withHandler(handler); new block or item types cannot be added because the interfaces are sealed over the static registry.
1.3 valence and ferrumc
- valence (Rust, MIT,
c4177cab47ae109c848a431756d5d7c0207c05ed, MC 1.21.1). Extractor output is committed undercrates/valence_generated/extracted/(6.3 MB) andcrates/valence_entity/extracted/, produced by theextractor/Fabric mod; the build scripts (crates/valence_generated/build/*.rs, 1,950 lines) parse it withinclude_str!and emit Rust withquote!. Blocks areBlockState(u16)andBlockKindenums; entities are bevy_ecs 0.14 bundles generated per type fromentities.json(valence_entity/build.rs, 783 lines). No behavior, loot, recipes or worldgen.README.md:21-27: "You can think of Valence as a game engine for Minecraft servers ... vanilla game mechanics are all expected to be built as optional plugins." No Mojang attribution for the JSON. - ferrumc (Rust, MIT,
51830e99675deae1f6cdbb62bf5d32b3d25c1c76, MC 1.21.8). 42 MB of JSON committed underassets/without provenance, includingdensity_function.jsonand Pumpkin's test fixture names (multi_noise_sample_no_blend_no_beard_0_0_0.json,density_function_tests.json). bevy_ecs 0.18; generated plain structs for blocks; no behavior hooks; own climate-driven terrain (docs/world-generation/terrain.md), not the datapack pipeline.README.md:298: "Pumpkin aims to match the vanilla server as closely as possible ... FerrumC prioritizes performance and takes a clean-room approach."
1.4 cuberite
C++, Apache-2.0, 7fd3fa5c9345a3f1b949c0988c4849db00a68486. The pre-flattening model, kept here as the extreme of hand-maintained data in code.
- Data:
Server/items.ini(1,461 lines ofname=id:damage),Server/crafting.txt(1,004 lines, grammar in the header),Server/furnace.txt(188),Server/brewing.txt(155),Server/monsters.ini; parsed bysrc/BlockType.cpp:30-50andsrc/CraftingRecipes.cpp:383-410. Block ids are a compile-timeenum ENUM_BLOCK_TYPEinsrc/BlockType.h. The only Mojang-derived input is the flattening palette:Tools/BlockTypePaletteGenerator/Generator.luaturns theblocks.jsondata generator report intoServer/Protocol/1.13/base.btp.txt, andsrc/Registries/BlockStates.h(25,485 lines) plussrc/Protocol/Palettes/Palette_1_13..1_16.cpp(55,508 lines) are generated from it. - Behavior: 100
Block*.hheaders;BlockHandler.cpp:199-450instantiates 251constexprhandler objects such ascBlockDoorHandler BlockAcaciaDoorHandler(E_BLOCK_ACACIA_DOOR)andcBlockHandler::For(BLOCKTYPE)dispatches through one exhaustive switch. Items mirror this (81 headers). 37cMonstersubclasses with hand-written AI.src/Generating/(69 files) is acComposableGeneratorconfigured byworld.ini, not a vanilla port. Drops areConvertToPickupsoverrides; no loot JSON. - Plugins: Lua with 72
HOOK_*entries (src/Bindings/PluginManager.h:82-144). Plugins intercept events; they cannot add block types.
1.5 Mojang's Java server, from public documentation
Sources: NeoForge docs (MIT), Fabric docs and wiki (CC BY-NC-SA 4.0), Minecraft Wiki (CC BY-NC-SA 3.0). No decompiled code was read. The pattern:
- One
Blocksingleton per id, one subclass per behavior family, tuned byBlockBehaviour.Properties(destroyTime,explosionResistance,sound,lightLevel(state -> n),friction,randomTicks(),requiresCorrectToolForDrops()). "there is only ever one of each block in the game" (https://docs.neoforged.net/docs/blocks/). About 1,300 block ids (https://minecraft.wiki/w/Java_Edition_data_values/Blocks) over fewer than 300 block classes (287 names ending inBlockin the 1.21.5net.minecraft.world.level.blockindex at https://mappings.dev/1.21.5/net/minecraft/world/level/block/index.html, a proxy count). BlockStateis immutable and interned ("== can and should be used to compare BlockStates"), built fromIntegerProperty,BooleanProperty,EnumPropertyincreateBlockStateDefinition(https://docs.neoforged.net/docs/blocks/states). The documented pipelines name the overridable hooks: placement (canBeReplaced,getStateForPlacement,canSurvive,onPlace,setPlacedBy), breaking (attack,getDestroyProgress,playerWillDestroy,onRemove,dropResources), ticking (tickviascheduleTick,randomTick), andneighborChanged.- Items:
Item.Propertiessets data components (stacksToisMAX_STACK_SIZE,durabilityisMAX_DAMAGE,food,useCooldown,enchantable,repairable,equippable). Since 1.21.2 tools are "a combination of the following fourteen data components" (TOOL,WEAPON,ATTRIBUTE_MODIFIERS, ...) rather than subclasses, with shears as the documented exception (https://docs.neoforged.net/docs/items/tools). The wiki lists 89 component headings, some grouping many variants (https://minecraft.wiki/w/Data_component_format). Storage is a prototypeDataComponentMapplus a patch map per stack. - Entities:
EntityType.Builder.of(factory, MobCategory).sized(w, h).eyeHeight(...).fireImmune().clientTrackingRange(n).updateInterval(n); attributes are doubles with defaults and modifiers; goals have integer priorities and "Mobs attempt to perform the lowest priority goal they can"; brains hold memories, sensors and activities (https://minecraft.wiki/w/Mob_AI). - Loot: data-driven tables with singleton and composite entries, 43 function types (https://minecraft.wiki/w/Item_modifier), 19 condition types (https://minecraft.wiki/w/Predicate), context sets per situation; mods add a type by registering a
MapCodec(https://docs.neoforged.net/docs/resources/server/loottables/custom/). Recipes:RecipeTypeplusRecipeSerializerregistries;RecipeManagerreloads with/reload. - Worldgen: 59 hardcoded feature types configured by JSON ("They are hardcoded, thus new ones cannot be added through datapacks", https://minecraft.wiki/w/Configured_feature), 19 placement modifiers, about 54 density function types, material rules, structure sets with
random_spreadorconcentric_rings, template pools and processor lists. Chunk statusesempty, structure_starts, structure_references, biomes, terrain, features, initialize_light, light, spawn, full. - Registries and packs: static registries freeze at startup; dynamic registries (
worldgen/*,damage_type,enchantment, variants) load at world load and are not reloadable; loot tables, recipes, tags, predicates and functions are. "If a file exists in multiple data packs only the file in the last data pack is used ... tag files without "replace": true merge" (https://minecraft.wiki/w/Data_pack).pack.mcmetamoved frompack_formattomin_formatandmax_formatin 25w31a (https://minecraft.wiki/w/Pack_format). The data generator (net.minecraft.data.Main --all) writes the vanilla data pack and reports (reports/blocks.jsonwith every state and its protocol id,registries.json).
1.6 Allay (AllayMC/Allay)
Java 21, LGPL-3.0 with data/ and codegen/ MIT, f223a12164f2fe0b594a6b83d950307012f23c64. The component model in detail.
- Declaration. A component is any class implementing a marker interface (
api/.../component/Component.java) with a static field annotated@Identifier.Componentnaming it (server/.../component/ComponentProvider.java:93-132walks the class and its superclasses for that field). A block type's behavior is aComponentClass(server/.../component/ComponentClass.java): its constructor runs every provider in list order, then injects components into the owner's component-typed fields by assignable type (exactly one match orComponentInjectException, lines 219-247), resolves@Dependencyfields by identifier or type (263-305), injects@Managerand@ComponentObject, registers each component on a per-objectAllayEventBus, and finally calls@OnInitFinishmethods (119-135). The docs describe it as "inspired by dependency injection framework like Guice" (docs/development/component-injection-system.md). - Dispatch has two tiers.
BlockBehaviorImpl(server/.../block/impl/BlockBehaviorImpl.java:38-48) holdsBlockBaseComponentandBlockStateDataComponentbehind Lombok@Delegate, soblockType.getBlockBehavior().place(...)is a direct interface call. Inside the base component, ten lifecycle points publish events to sibling components (BlockBaseComponentImpl.java:89-380:CBlockOnNeighborUpdateEvent,CBlockRandomUpdateEvent,CBlockOnPlaceEvent,CBlockAfterPlacedEvent,CBlockOnReplaceEvent,CBlockOnPunchEvent,CBlockOnInteractEventwith a mutablesuccessflag, ...). Handlers are@EventHandlermethods found by reflection, sorted by priority and invoked withMethod.invoke(eventbus/MethodEventHandler.java:72). Counts: 10 block, 10 item and 13 entity component events; 11 block, 15 item and 46 entity@EventHandlermethods in the server. - Lifetime. Block components are singletons per block type, shared by all states:
AllayBlockType.Builder.build()wraps each asComponentProvider.of($ -> singleton, ...)(AllayBlockType.java:695), so state-dependent behavior branches on theBlockStateargument. Item stacks and entities get fresh components per instance from suppliers (EntityTypeInitializer.java:286-335). - Composition.
BlockBehaviorisBlockBaseComponentplusBlockStateDataComponent(api/.../block/BlockBehavior.java:14-17). The API has 16 block component interfaces (BlockFallableComponent,BlockLiquidBaseComponent,BlockOxidationComponent,BlockSlabBaseComponent, ...), the server 197 implementation files of which 173 are*BaseComponentImpl(door 228 lines, copper door 130). A door isdoorBuilder(BlockDoorBehaviorImpl.class, blockId, BlockDoorBaseComponentImpl::new).build()(BlockTypeInitializer.java:1429-1431); a copper door adds.addComponent(new BlockOxidationComponentImpl(level, fn))(1438-1442).vanillaBlock(BlockId)(436-452) attaches per-state data fromblock_states.json, tags fromblock_types.jsonand the default state hash, and throws if the id has no data. - Coverage.
BlockTypeInitializerassigns 1035 types by hand; the generatedBlockTypeDefaultInitializerhas 1477 null-guarded fallbacks. 366 distinct behavior classes are referenced; the largest alias groups areBlockElementBehaviorImpl(120),BlockSlabBehaviorImpl(93),BlockDoubleSlabBehaviorImpl(93),BlockStairsBehaviorImpl(89) andBlockWoodBehaviorImpl(50).codegen/.../BlockClassGen.java:95-150writes an empty interface and impl perBlockIdonlyif (!Files.exists(...)), then deletes files whose ids disappeared. - Entities. A zombie is nine components: humanlike base, container holder, baby, undead, zombie living, physics, head yaw, parallel tick, and
EntityAIComponentImplbuilt from aBehaviorGroupImplwith aNearestPlayerSensor, twoMeleeAttackExecutorbehaviors at priorities 3 and 2, aFlatRandomRoamExecutorat 1, controllers and an A* route finder (EntityTypeInitializer.java:286-335). The AI runs from@EventHandler onParallelTick(CEntityParallelTickEvent)(EntityAIComponentImpl.java:84-100). Nothing readsminecraft:behavior.*. - Data.
data/resources/block_states.json(11 MB) is one row per state withhardness,explosionResistance,friction,lightDampening,lightEmission,mapColor,collisionShape,shape,burnOdds,flameOdds,requiresCorrectToolForDrops,isSolid,translucency,liquidReactionOnTouchandblockStateHash;block_types.jsonhasdefaultBlockStateHashandtags;items.jsonhasarmorValue,attackDamage,enchantValue,furnaceBurnDuration,id,maxDamage,maxStackSize,tags.data/resources/unpacked/(41 MB) holds the raw dumps. - Packs.
PackRegistryLoaderscansresource_packs/and plugin directories;BehaviorPackhasType.DATAand is only forwarded to clients. Nothing interprets pack JSON server-side. - Plugin API.
AllayBlockType.builder(MyBlockImpl.class).identifier("myplugin:ruby_block").customBlockDefinition(CustomBlockStateDefinition.ofTexture("ruby_block")).build()(docs/advanced/custom-block-api.md:44-47); state-dependent geometry via a function per state;rawComponentsfor anything the API lacks; requires the server module (apiOnly = false). Custom items mirror it (docs/advanced/custom-item-api.md:33-35). Plugin components get the same events as vanilla ones. - Tests: section 4.
1.7 Dragonfly (df-mc/dragonfly)
Go, MIT, 1bf4e242c81f1f66ba6bdf8fcdcc80c70125a499.
- Interfaces. 44 block-side interfaces (
server/block/block.go:17-118:Activatable,Pickable,Punchable,LightEmitter,LightDiffuser,Replaceable,EntityLander,EntityInsider,EntityStepper,Frictional,Permutable;block.go:274-288:Sleepable,Flammable;break_info.go:18-23:Breakable;hopper.go,container.go,explosion.go, ...) and world-side ones the tick loop needs (server/world/block.go:50-72Liquid; 132-181RandomTicker,ScheduledTicker,TickerBlock,NeighbourUpdateTicker,NBTer,LiquidDisplacer;redstone.go:55-108ten redstone interfaces). 43 item interfaces (server/item/item.go:16-202:UsableOnBlock,UsableOnEntity,Consumable,Weapon,Cooldown,Releasable, ...;durability.goDurable,Repairable;enchantment.goEnchantable). - Dispatch is a type assertion at each call site:
tick.go:115-121for neighbor updates, 186-193 for random and block entity ticks, 318 guards a scheduled tick with the block hash so a replaced block does not get a stale tick; about 50 assertions inserver/player/player.goand 62 inserver/session/.BasicBlockRegistry.Finalize(server/world/block_registry.go:329-352) asserts each block once and packs light filter, light emission andNBT|RandomTick|Liquid|LiquidDisplacingflags into auint16per runtime id, so lighting and random tick selection never assert. - Implementer counts over 261 struct types:
BreakInfo()208,UseOnBlock94,NeighbourUpdateTick71,Activate33,RandomTick31,FlammabilityInfo28,ScheduledTick8. Shared behavior comes from embedded helper structs (transparentin 87 types,solidin 126,sourceWaterDisplacerin 48). - Identity. Two hashes derived from the Go value.
server/block/hash.gois generated bycmd/blockhash(225Hash()methods packing fields into bits,wood_door.go:1136-1138);network_block_hash.go:16-96re-serializes{name, states}as sorted little-endian NBT and takes FNV-1a-32, withminecraft:unknownfixed at0xfffffffe.block_state.go:24-35registers every palette state from the embeddedblock_states.nbtas anunknownBlockplaceholder;register.gomakes 245RegisterBlockcalls that enumerate permutations (allDoors()inwood_door.go:149-163);RegisterBlockpanics "block state returned is not registered" or "already registered" (block_registry.go:294,297);Finalizesorts states byfnv1.HashString64(name)to match the vanilla palette order and panics on hash collision. Decoding never parses NBT into fields:blocks[rid]is already the registered value, which is why every permutation must exist up front (Permutabledocs,block.go:113-121). - Items, creative and recipes:
RegisterItempanics when a name is missing from the embeddedvanilla_items.nbt;creative.go:96-123silently skips creative entries whose item is not registered, so the creative menu self-trims;recipe/vanilla.go:13-20embedscrafting_data.nbt,smithing_data.nbt,smithing_trim_data.nbt,potion_data.nbtand skips any recipe with an unresolvable input. - Entities: 16 types (
server/entity/register.go:12-29), zero mobs, behavior as config structs (ProjectileBehaviourConfig,PassiveBehaviourConfig). Worldgen:NopGeneratorandgenerator/flat.go; 90 hand-written biome structs. Tests: 10_test.gofiles, none against vanilla data; the panics are the check. - Custom content:
CustomBlockandCustomBlockBuildable(server/world/block.go:30-47),server/block/customblock/properties and permutations,server/server.go:398-411emitsprotocol.BlockEntrywith runtime ids past the vanilla palette, andserver/internal/packbuilder/blocks.go:27-55writes a resource pack from the Go values at startup. - No written rationale; the registry comment (
block_registry.go:248-250) is the design note: "We assume all blocks must have all their states registered beforehand."
1.8 PowerNukkitX
Java, LGPL-3.0, 37caa9e66690f2d327b33d8a72e7621377ab96c5, root package org.powernukkitx. 1327 Block*.java, 186 Entity*.java.
- Properties.
block/BlockProperties.java:12-60sums the bit sizes of itsBlockPropertyTypes, enumerates the cartesian product when the total is at most 16 bits and throws otherwise.BlockChest.java:33declaresnew BlockProperties(CHEST, CommonBlockProperties.MINECRAFT_CARDINAL_DIRECTION);BlockWoodenDoor.java:9addsOPEN_BIT, UPPER_BLOCK_BIT, DOOR_HINGE_BIT.CommonBlockPropertieshas 146 shared constants.HashUtils.computeBlockStateHashis the same FNV-1a over sorted NBT.BlockRegistry.java:1508-1545enforces by reflection that each class has astatic final PROPERTIESfield and overridesgetProperties(), with a copy-pasteable fix in the exception message. - Registries.
registry/Registries.javaholds 22 singletons. Data comes from the Maven artifactorg.powernukkitx:gamedata(r-26_u5-SNAPSHOT):gamedata/kaooot/{block_palette.nbt, block_definitions.nbt, item_palette.json, item_components.nbt, item_tags.json, creative_items.json, recipes.json, entity_identifiers.nbt, biomes.json, biome_definitions.nbt, trim_data.json, voxel_shapes.json, jigsaw_structure_data.nbt}.loadDataDrivenProperties()(1478-1490) keepsblock_definitions.nbtonly to "hand it on join". Nothing reads entity, loot, spawn rule or recipe JSON from a behavior pack. - AI is a Java builder per mob (
entity/mob/EntityZombie.java:67-95:EntityAI.of(this).behavior(new MeleeAttackExecutor(...)).when(...).sensors(new AttackSensor(), new NearestPlayerSensor(40, 0, 0)).controllers(...).routeFinder(...)), with 76 executors, 19 sensors, 18 evaluators and 12 controllers.minecraft:behavior.has zero hits in main sources. - Loot:
level/loot/VanillaLootTables.java(128 lines) hand-codes three stronghold chest tables keyed by Mojang's path strings. Trading: 167TradeRecipeBuildUtils.of(...)calls inentity/data/profession/. Recipes:RecipeRegistry.java:672-682reads Kaooot'srecipes.jsonand expands tag inputs throughitem_tags.json. - Generator: 16
densityfunction/classes each citing misode/mcmeta in an@implNote(DensityContinents.java:8links.../worldgen/density_function/overworld/continents.json), 172 feature classes, 57 populators; nonoise_settingsJSON is read, the JSON was transcribed into Java. - Plugin API:
BlockRegistry.registerCustomBlock(Plugin, Class<? extends Block>...)(1568-1620) requiresCustomBlock;CustomBlockDefinition.Builder(145-312) assembles aCompoundTagof Mojang component names (minecraft:friction,minecraft:destructible_by_mining,minecraft:light_emission) for the client, while server behavior stays in the Java class. - Tests: section 4.
1.9 CloudburstMC/Server
Java, server GPL-3.0 and api LGPL-3.0, b54033d5293689b3e286ab20814163fe2ccb2d9d, active (last commit 2026-10-22, "Support importing BDS worlds"). The BlockBehavior class hierarchy the earlier design had is gone; the current design is component keys bound to lambdas:
api/.../block/BlockTraits.javahas 120 constants mapping API names to vanilla state names (BooleanBlockTrait.of("is_open", "open_bit")at line 578,CARDINAL_DIRECTIONat 48).BlockStateis immutable withwithTrait(BlockState.java:64-102).api/.../block/BlockComponents.javadeclares 65ComponentTypes (AFTER_BREAK,ATTACK,CAN_BE_USED,CAN_RANDOM_TICK,USE,ON_NEIGHBOUR_CHANGED,GET_LOOT,ON_PLACE,ON_DESTROY, ...). Handlers are stateless lambdas in 80 files underserver/.../block/component/;registry/VanillaBlockBehaviors.java(1,057 lines, 379.set(...), 118configure(...)) binds them, for exampleconfigureDoorat 708-716 setsON_PLACE,CAN_BE_USED,GET_LOOT,USE,ON_NEIGHBOUR_CHANGED,ON_DESTROY. Items have 38ItemBehaviorsconstants; entities useEntityFactorywith 168 classes and no AI package.- Numeric block properties are data:
server/.../block/BlockPropertyData.java:28-70loadsdata/blocks.jsonkeyed byblockStateHashand throws on duplicates.data/is the CloudburstMC/Data submodule on branchtemp/26.50-geometry, the same source this repository already pins. The palette passes through theblock-state-updaterlibrary (BlockPalette.java:258-283). No behavior pack JSON is read; generators are YAML presets underserver/src/main/resources/generator/. - No README or design document explains the move or the earlier stall.
1.10 Nukkit (CloudburstMC/Nukkit)
Java, GPL-3.0, b3caad67d86be34aeca3937e64e8176f6760a2d4. 751 Block*.java, 322 Item*.java. block/Block.java:48-56 holds static parallel arrays (list, fullList, light, lightFilter, solid, hardness, transparent); init() (72-110) reflectively instantiates every class for every meta value and stores fullList[(id * CACHED_DATA_SIZE) | data]; Blocks.java has 712 list[STONE] = BlockStone.class entries. Overridable methods: onUpdate(int) 303, onActivate 307, getHardness 315, getResistance 319, getToolType 331, getId 405, getDrops 453. Entity.registerEntity(name, class) (1070-1074) with 116 calls. Resources are network dumps (runtime_block_states.dat, recipes.json, creative_items.json, entity_identifiers.dat, biome_definitions.dat). This is the pattern this server's block/blocks/ and entity/ follow today.
1.11 Mojang's Script API as a data model
From https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/ (page names in parentheses).
- Blocks:
BlockPermutation.resolve(name, states),getState,getAllStates,withState,matches,getTags,getItemStack(blockpermutation);BlockTypes.getAll();BlockStates.getAll()returnsBlockStateTypewithvalidValues, the global state domains (blockstatetype).BlockComponentTypeslists 15 ids (fluid_container,inventory,map_color,movable,piston,record_player,redstone_producer,sign,recipe_crafting, ...). - Items:
ItemComponentTypeslists 11 (book,compostable,cooldown,durability,dyeable,enchantable,food,inventory,potion, ...).ItemDurabilityComponentexposesmaxDurabilitybut its page says "this component only applies to data-driven items" (itemdurabilitycomponent).ItemFoodComponentexposesnutrition,saturationModifier,canAlwaysEat,usingConvertsTo;ItemCooldownComponentexposescooldownCategoryandcooldownTicks;ItemEnchantableComponentexposesslots. - Entities:
EntityComponentTypeshas 68 members (health,movement,type_family,scale,variant,is_baby,tameable,inventory,equippable,projectile,rideable,leashable,ageable,breathable,navigation.*,movement.*, flag componentsis_*,can_*,fire_immune). There is nominecraft:attack. Seven areEntityAttributeComponents withcurrentValue,defaultValue,effectiveMax,effectiveMin(health, movement, lava and underwater movement, exhaustion, hunger, saturation).Entity.triggerEvent(name)fires JSON component group events;getPropertyandsetPropertyread entity properties. - Events:
world.afterEventshas 72 signals andworld.beforeEvents18 cancellable ones (playerBreakBlock,playerPlaceBlock,playerInteractWithBlock,itemUse,entityHurt,explosion, ...). - Extension:
system.beforeEvents.startupexposesblockComponentRegistry.registerCustomComponent(name, hooks)anditemComponentRegistry.registerCustomComponent(name, hooks). Block hooks (15):beforeOnPlayerPlace,onBlockStateChange,onBreak,onEntity,onEntityFallOn,onNamedTick,onNeighborChanged,onPlace,onPlayerBreak,onPlayerInteract,onRandomTick,onRedstoneUpdate,onStepOff,onStepOn,onTick, each(event, CustomComponentParameters). Item hooks (7):onBeforeDurabilityDamage,onCompleteUse,onConsume,onHitEntity,onMineBlock,onUse,onUseOn. The overview (https://learn.microsoft.com/en-us/minecraft/creator/documents/customcomponents) states the dispatch rules: custom components are listed incomponentsbesideminecraft:loot; "for each type of event, the event is sent to the custom components in the order they are listed"; "a component on a permutation overrides the component on the base block"; "a custom component can be registered in one behavior pack, and then used from another."
1.12 PrismarineJS/minecraft-data
JavaScript data repository, 5e063fa99cfe8ded065ec6788b66696b7e4ef143. README.md:148-153 says "MIT" and "Some of the data was extracted manually or automatically from wiki.vg and minecraft.gamepedia.com. If required by one of the sources the license might change"; the clone has no LICENSE file and no root package.json, and the npm package is built from PrismarineJS/node-minecraft-data. The CC0 premise in the task is not supported by this checkout.
- Coverage.
data/bedrock/has 57 version directories; the newest with game data is 1.26.30 (1,356 blocks, 16,913 states inblockStates.json, 1,933 items). 1.26.40 to 1.26.51 carry only protocol files.data/dataPaths.jsonforbedrock.1.26.51aliasesblocks,blockStates,blockCollisionShapes,itemsto 1.26.30,entitiesto 1.21.80,biomesto 1.21.60,recipesto 1.19.10,enchantmentsto 1.19.1, andmaterialsandeffectsto Javapc/1.17. - Shape. A block:
{"id": 49, "name": "oak_log", "displayName": "Oak Log", "hardness": 2, "resistance": 2, "stackSize": 64, "diggable": true, "material": "mineable/axe", "transparent": false, "emitLight": 0, "filterLight": 15, "defaultState": 1529, "minStateId": 1529, "maxStateId": 1531, "drops": [161], "boundingBox": "block"}; no Bedrock block carriesstates, those live inblockStates.jsonas{"name": "oak_log", "states": {"pillar_axis": {"type": "string", "value": "y"}}, "version": 18168865}indexed by palette position. An item:{"id": 347, "stackSize": 1, "name": "diamond_sword", "enchantCategories": [...], "repairWith": ["diamond"], "maxDurability": 1561, "nbt": {...}, "version": "none"};data_drivenitems carry their component NBT. An entity:{"id": 23, "internalId": 32, "name": "zombie", "height": 1.8, "width": 0.6, "type": "hostile", "category": "Hostile mobs"}. Schemas inschemas/*_schema.jsonare validated bytools/js/test/test.jswith Ajv, andbedrock_block_states.jschecks thatminStateId..maxStateIdranges tile the state list exactly. - Data quality. In the 1.26.30
blocks.json, none of the 22harvestToolsids and none of the 1,004 integerdropsresolve to the expected item in the 1.26.30items.json(stone drops[62], which islit_furnace); in 1.19.1 to 1.21.111 they do. The schema cannot catch this becausedropsonly requires integers. - Provenance (
doc/bedrock.md:28-46):blocks.json,items.json,entities.jsonandblockStates.jsonfrom "bedrock-extractor" (https://github.com/extremeheat/minecraft-data-extractor/tree/master/bedrock, not in this clone set);recipes.jsonfrom the "bedrock-protocol dumper";biomes.json"via Amulet";enchantments.json"via Geyser -> pc data";blockCollisionShapes.json,materials.json,effects.json"(from pc data)";language.jsonfrom CreeperG16/extract-minecraft-lang;protocol.json"manual updated". - Update process. Detection lives in bedrock-protocol:
.github/workflows/update-helper.ymlruns every 2 hours,.github/helper-bot/index.js:6polls the iTunes lookup forcom.mojang.minecraftpe, line 92 callsbedrockServer.getPongDetails(version)(boots a real BDS and reads the protocol number), opens an issue and dispatches minecraft-data'sbedrock-version-bump.yml, which runstools/js/incrementVersion.jsto clone the previousdataPathsentry and open a PR. Humans then editlatest/proto.yml. Block and item data arrive in separate PRs ("Add bedrock block data for 1.26.0-1.26.30 (#1192)"), which is why newer versions alias older game data.
1.13 prismarine-registry, prismarine-block, prismarine-item, prismarine-entity, prismarine-chunk
All MIT. Commits 69d2fb05, 8c8d4e02, 2b148a38, 4f2678f7, a08dc2c6.
prismarine-registry/lib/bedrock/index.jsaddshandleStartGame(packet):loadItemStates(packet.itemstates)rebuilds the item tables from the packet, andloadHashedRuntimeIdsfillsblocksByRuntimeId[Block.getHash(name, states)]for everyblockStatesentry whenblock_network_ids_are_hashesis set. Its Bedrock tests areit.skip("times out").prismarine-block/index.js(434 lines) is oneBlockclass whose constructor copieshardness,displayName,shapes,boundingBox,transparent,diggable,material,harvestTools,dropsfromregistry.blocksByStateId[stateId](126-156); Bedrock properties come fromregistry.blockStates[stateId].states(190-194);fromPropertiesscansminStateId..maxStateIdlinearly (249-256);getHashsorts keys, writes little-endian NBT and runs FNV-1a-32 (288-301, 426-433);digTime(311-385) multiplies byregistry.materials[material][heldItemType], which on Bedrock comes frompc/1.17. No class per block exists anywhere in the stack.prismarine-itemwrites the Bedrock network item (network_id,count,metadata,stack_id,extra.nbt) intoNotch(127-157).prismarine-entityis a 79-lineEventEmitterwith position, metadata and equipment fields.prismarine-chunk/src/index.jsmaps Bedrock majors1.18to1.21ontosrc/bedrock/1.18/chunk; there is no1.26entry, sorequire('prismarine-chunk')('bedrock_1.26.51')throws.1.3/SubChunk.js:73-139decodes the palette type byte (usingNetworkRuntimeIds = paletteType & 1,bitsPerBlock = paletteType >> 1), resolves runtime palettes through the registry and persistent palettes throughBlock.fromProperties. Tests load capturedlevel_chunkandsubchunkpackets for 1.16.220 to 1.19.1 and assert that re-encoding reproduces the same blob hashes (test/bedrock.test.js).
1.14 flying-squid
JavaScript Java server, MIT, fa022792d30e79f96c8c8dbf659fc61ae4177b4f. 44 plugin files in src/lib/plugins/. digging.js:71 uses block.digTime(...) and 158-175 drops from registry.blockLoot[name].drops or block.drops[0]; placeBlock.js maps item names to registry.blocksByName; world.js sets blocks by registry.blocks[id].minStateId. No recipes, no mob AI (spawn.js:79-95 sets gravity and health only). Tests drive real mineflayer bots against the server for 23 versions (test/mineflayer.test.js, 448 lines).
1.15 minecraft-bedrock-server and bedrock-protocol
extremeheat/minecraft-bedrock-server(MIT,1aae0086d1ac2a702741c7e5c808780935306acc; thePrismarineJS/minecraft-bedrock-servername in the task does not exist).src/index.js(330 lines):getLatestVersions()(35-57) readshttps://net-secondary.web.minecraft-services.net/api/v1.0/download/links;download()(62-118) probeshttps://www.minecraft.net/bedrockdedicatedserver/bin-<os>/bedrock-server-<v>.zipwith HEAD requests over 30 build suffixes, downloads with a 20 shttps.get, and unzips; it sets noUser-Agentand has no EULA prompt.configure()(128-145) appendsonline-mode=false,allow-list=false,server-port=19130and user options toserver.properties;startServer()(171-200) fires when stdout containsServer started;getPongDetails()readstransport=and pings RakNet on 19130 or NetherNet on 7551.test/test.jsboots 1.16.210, 1.18.0, 1.21.80 and 1.26.51 with an 80 s start budget.PrismarineJS/bedrock-protocol(MIT,de199f0fe7ade6477c5637851ea814a65cffabe9).src/options.jspinsCURRENT_VERSION = '1.26.51'and lists 54 versions, 19 of them tested on CI.test/internal.test.jsboots BDS per version throughtools/startVanillaServer.js(startServerAndWait2(version, 220000, ...)), runs a spawn test anddumpPacketsin parallel, then replays the dumped packets through bedrock-protocol's ownServer. CI runs on Ubuntu and Windows withtimeout-minutes: 14. Its tools dump packets for tests; none produce minecraft-datablocks.jsonoritems.json.
1.16 misode/deepslate
TypeScript, MIT, c33b4a65f2ad474b14aab71061fbd7766b675fad, version 0.27.2, 123 files and 13,253 lines. Another agent covers its worldgen depth; this is the overall design.
src/core/Registry.ts:5-21:Registry<T>keyed byIdentifierwith an optionalparser: (obj: unknown) => T, lazy thunk values, abuiltinmap that survivesclear(), and a lazily created tag registry.Holder.parser(registry, direct)(Holder.ts:4-18) implements "a string is a reference, anything else is inline";HolderSet.fromJson(HolderSet.ts:28-58) parses tag files withreplaceandrequired.WorldgenRegistries.ts:9-14registersworldgen/noise,worldgen/density_function,worldgen/noise_settings,worldgen/material_rule,worldgen/material_conditionand a parserlessworldgen/biome.BlockStateis anIdentifierplus untyped string properties, withparse,fromNbt,fromJson; there is no block definition, no state domain validation.ItemStackcarriesMap<string, NbtTag>components and asks a consumer-suppliedItemComponentsProvider(ItemStack.ts:7-9) for base components. No loot, recipe, feature or biome objects; no ticking or interaction code.- Vanilla data is not shipped (
files: ["lib/*", "dist/*"]); the demo fetcheshttps://raw.githubusercontent.com/misode/mcmeta/at runtime (demo/main.ts:65-78). Tests assert inline expected numbers (DensityFunction.test.ts:43-48toBeCloseTo(0.3004295819443726)) and an ASCII slice of a generated chunk, not stored vanilla fixtures.
1.17 bridge. and Blockception schemas
- bridge-core/editor (GPL-3.0,
8eaccaf8eceb31a2a9cce51a392b7d758220909c) holds no schemas.src/libs/data/Data.ts:46-51fetcheshttps://raw.githubusercontent.com/bridge-core/editor-packages/release/packages.zipat runtime. That repository is MIT (https://raw.githubusercontent.com/bridge-core/editor-packages/main/LICENSE, GitHubspdx_id: MIT), as is the compilerbridge-core/dash-compiler. The schemas select a version table with JSON Schemaif/thenonformat_version(packages/minecraftBedrock/schema/entity/main.json:"1.8.0" ... "1.16.0"to./v1.16.0/main.json,"1.16.100"to./v1.16.100/main.json, 38 entity folders up tov1.26.20);formatVersions.jsonlists 55 versions withcurrentStable: "1.26.20". The dash-compiler custom component plugin (src/Plugins/BuiltIn/Components/Plugins.ts,Component.ts) is a build-time macro: a JS file undercomponents/<block|item|entity>/deep-merges generated vanilla JSON into the host file. That is the opposite of Mojang's runtime hooks. - Blockception/Minecraft-bedrock-json-schemas (BSD-3-Clause,
473d1aa94be63806a4f38345ad461b5d4e8d8a2a). 1,159 source schemas undersource/behavior/(entities 502, of which 177 behaviors and 211 components; blocks 47; items 47; loot_tables 50; features 33; spawn_rules 23; biomes 17; recipes 11) andsource/general/. One schema per file type, not per version:source/general/format_version.jsonis a string matching^([1-9]+)\.([0-9]+)\.([0-9]+)$with default1.26.0, and the payload is a latest-superset. Component descriptions carry defaults and caveats, for examplebehaviors/melee_attack.jsondocumentscooldown_time1,melee_fov90 and marksmax_distas "Unused". Compiled byDaanV2/Json-Schema-Action-Compressionin CI.
2. Patterns and trade-offs
Vanilla Bedrock has 1463 blocks and roughly 60 distinct behaviors (Allay's 366 behavior classes collapse to about 60 once alias groups such as the 120 element blocks, 93 slabs and 89 stairs are counted once; Pumpkin covers 1,286 Java blocks with 32 tag-wide behaviors plus 124 singles). The four patterns handle that shape differently.
(a) Class per thing with inherited methods
Used by Nukkit (751 classes), PowerNukkitX (1327), cuberite (251 handler objects over 100 classes, dispatched by an exhaustive switch), and this server (260 block, 65 item, 111 entity classes).
- Shape fit: each of the 1463 blocks needs a class or a constructor argument. PNX reaches 1327 classes by subclassing per family and copying per alias; cuberite reuses one handler class with the block id as a constructor argument. Properties are overridable methods, so table-driven values need a class per row.
- Plugins subclass and register; PNX enforces a
PROPERTIESconvention by reflection and needs the class to carry its own id. - Dispatch is a virtual call, the cheapest of the four.
- Testability: instantiate and call; but field initializers that call overridable methods (this server's
item/Item.ts:29, noted in the earlier note) make definitions arrive too late. - Bedrock JSON mapping: poor. Components become attributes of a class, component groups and events have no home, and permutations become
if (state.x)branches inside methods.
(b) Immutable definition plus a behavior type per family
Used by Mojang Java (about 1,300 ids over fewer than 300 classes, builder properties per id), Pumpkin (generated constants plus BlockBehaviour per id or tag), Dragonfly (struct per block value, Go interfaces per capability, type assertions at call sites), Minestom (record plus nullable BlockHandler).
- Shape fit: good. Pumpkin's
#[pumpkin_block_from_tag("minecraft:doors")]binds one behavior to every door; Mojang'sBlocksclass passes the sameDoorBlock::newto every wood. Dragonfly pays for Go's lack of generics over state by enumerating permutations (allDoors()) and generatingHash()per type. - Plugins register a behavior for ids or tags (Pumpkin) or attach a handler per placed block (Minestom). Pumpkin's registry is sealed after
default_registry(), so plugins get events only. - Dispatch: Pumpkin uses a dense
[u16; COUNT]index to anArc<dyn>; Dragonfly asserts per call site and flattens hot flags into auint16per runtime id atFinalize. Both show that load-time precomputation, not the pattern, decides the hot path cost. - Testability: good. Definitions are data; a family behavior is unit-tested once; Pumpkin's chunk dumps test the whole pipeline.
- Bedrock JSON mapping: the definition side maps directly onto the 1.26.50 block files (
destructible_by_mining,map_color,loot,redstone_conductivity) and ontomojang-blocks.jsonfor states. The behavior side is a code mapping keyed by family, which is whatdescription.internal_vanilla_data.block_archetype(slab_block,stair_block) in those files already names. Component groups and events do not apply to blocks in this model.
(c) Definition plus a component list dispatched by event
Used by Allay (component singletons wired by reflection, per-object event bus), CloudburstMC/Server (65 ComponentType keys to lambdas), Mojang Bedrock itself (JSON components, 15 block and 7 item custom component hooks, list-order dispatch, permutation override), Java 1.21.2+ item data components, and Minestom's DataComponentMap for items (data only).
- Shape fit: best. Allay covers 1477 types with 173 base component implementations plus small add-on components (
BlockOxidationComponentImplon copper doors,BlockBlockEntityHolderComponentImplon chests); Cloudburst'sconfigureDooris six key-to-lambda bindings. Both keep one heavy base behavior per archetype and compose the rest, which is how Mojang's JSON reads too. - Plugins add a component to an existing type or build a new type from components (
AllayBlockType.builder(...).addComponent(...),registry.configure(type).set(KEY, handler)), and plugin components receive the same events as vanilla ones. This is also the shape of Mojang'sregisterCustomComponent. - Dispatch: Cloudburst does a map lookup by key per hook call. Allay delegates the base component directly (
@Delegate) and only pays reflection (Method.invoke, priority-sorted) for sibling components on ten block events; it never measured that cost in its docs. Ordering between components needs a rule: Mojang uses list order, Allay a priority integer. - Testability: each component is tested against a fake owner; Allay's
BlockStateDataTestchecks that every registered state has data. The risk is interaction bugs between components that only appear in combination. - Bedrock JSON mapping: direct. Component names are the keys; component groups are sets of components added or removed by events (entities); permutations are per-state component overrides, which Mojang documents for its own blocks. A component the server does not implement becomes a logged name instead of a missing class.
(d) Full ECS
Used by valence and ferrumc (bevy_ecs).
- Shape fit: entities fit (a bundle per type generated from
entities.json); blocks and item stacks do not, since they are values in chunk storage and inventories, not entities. Neither project implements vanilla behavior, so the pattern is unproven for parity. - Plugins add systems; this is the strongest extension model of the four.
- Dispatch is archetype iteration, cache-friendly for per-tick systems and awkward for one-off interactions (a right click is an event, not a frame).
- Testability: systems run against a
Worldfixture. - Bedrock JSON mapping: entity components map one-to-one (
minecraft:healthto aHealthcomponent), and component groups map ontoinsertandremoveat runtime. In TypeScript the typed-array ECS libraries force numeric component data, and the gain is per-tick throughput for thousands of entities, not correctness.
What all of them share
Identity is always data from a dump or generator (palette NBT, blocks.json report), hashed with FNV-1a-32 over sorted {name, states} NBT in every Bedrock project (Dragonfly network_block_hash.go, PNX HashUtils, Allay HashUtils, prismarine-block getHash). Shells are always generated (Allay BlockClassGen, Pumpkin pumpkin-codegen, Minestom code-generators, cuberite's palette Lua, Dragonfly cmd/blockhash). Behavior is always code, and the open design choice is only how that code is keyed to the data.
3. Pack loading and stacking precedents
What the Bedrock servers do
None of Allay, PowerNukkitX, Dragonfly, Cloudburst or Nukkit interprets a behavior pack server-side. Allay's PackRegistryLoader discovers packs in resource_packs/ and plugin jars and tags behavior packs Type.DATA for transfer to clients; PNX's ResourcePackManager serves packs and its only built-in behavior pack is ChemistryBehaviorPack; Dragonfly generates a resource pack from Go custom blocks at startup. The vanilla data these servers run on is compiled in.
What Pumpkin does for Java datapacks
DatapackManager.load_all scans <world>/datapacks, reads the enabled list from level.dat (data_packs.enabled, ids file/<dir>), loads embedded content first so disk packs override by id, and loads recipes, functions, loot tables, structures, template pools, processor lists, damage types, trades and generic dynamic registries. pack_format is read but never rejected (unwrap_or(61)). Feature flags (minecraft:vanilla, trade_rebalance) come from enabled pack names (mod.rs:602-649). Plugins can list, enable, disable and reload packs (pumpkin-plugin-api/src/datapack.rs). Minestom's only override is a same-named file in the working directory shadowing the classpath JSON.
What Mojang documents for Java
Last enabled pack wins per file; tags merge unless "replace": true; /reload covers loot tables, recipes, tags, predicates and functions but not dynamic registries; pack.mcmeta carries min_format and max_format since 25w31a with optional overlays for multi-version packs (https://minecraft.wiki/w/Data_pack, https://minecraft.wiki/w/Pack_format).
What Mojang documents for Bedrock
- Stacking: "Pack Stacking is how content is loaded on top of Vanilla content, causing each object that has the same name in both packs to be overwritten by the latest applied pack" (https://learn.microsoft.com/en-us/minecraft/creator/documents/resourcepack). "when content is loaded, vanilla content is loaded first, followed by any Add-Ons" (https://learn.microsoft.com/en-us/minecraft/creator/documents/behaviorpackfromscratch). The cooperative add-on guidance admits that when two packs change the creeper "one will 'win' (based on pack stack order, which for most players is not set in any particular intentful order)" (https://learn.microsoft.com/en-us/minecraft/creator/documents/practices/guidelinesforbuildingcooperativeaddons).
- Manifest:
format_version2 for behavior and resource packs, 3 in preview with semver strings;header.versiondecides replacement on import ("replace the old one if the version is higher, and ignored if it's the same or lower");dependencies[]are either{uuid, version}for packs or{module_name: "@minecraft/server", version: "1.9.0"}for script modules;header.min_engine_version"helps the game identify whether any backwards compatibility is needed" (https://learn.microsoft.com/en-us/minecraft/creator/reference/content/addonsreference/examples/addonmanifest). Subpacks: "Files added to sub-packs will override the files in the main resource pack" (https://learn.microsoft.com/en-us/minecraft/creator/documents/buildingsubpacks). - Per-file
format_version: "a format number at the top of all JSON files which represents the JSON data contract of that particular file. This number looks like a game release version number, but is not ... Files made in older formats will still work in newer versions of the game, but only if the format version is set correctly." Same-identifier definitions are chosen bymin_engine_version: "The definition with the same or closest and not greater min_engine_version, as compared to the top resource pack's engine version, will be parsed; all other definitions with the same identifier will not be parsed" (https://learn.microsoft.com/en-us/minecraft/creator/reference/content/versiondisambiguation). "Items defined with version 1.10 may have implicit behaviors not shown in the file, so these items can only be overwritten by other items defined by version 1.10" (https://learn.microsoft.com/en-us/minecraft/creator/documents/overwritingassets). world_behavior_packs.json: the only Learn description is "Contains behavior pack ID and version" besideworld_behavior_pack_history.json(https://learn.microsoft.com/en-us/minecraft/creator/documents/createaworldtemplate). The{pack_id, version}array shape and its ordering semantics are not documented on Learn; Blockception'ssource/general/world_x_packs.jsonis the closest formal description. BDS adds a server-levelbehavior_packs/folder: "Packs installed in a folder for a world are only available when that world is running on the server. Packs installed in the top level folders are available on any world" (https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockserver/getting-started).- Custom components: "a custom component can be registered in one behavior pack, and then used from another", and the hook order is the component list order (https://learn.microsoft.com/en-us/minecraft/creator/documents/customcomponents).
- BDS's own vanilla content is itself a stack of 56 layered packs composed by file name (section 2 of the earlier note), so the server's internal data loader and a user-pack loader are the same mechanism.
Schema validation of packs
Two models exist. bridge. keeps one schema per format_version and selects it with if/then (38 entity versions). Blockception keeps one latest-superset schema per file type with a format_version pattern and per-component defaults and caveats. A server loader needs bridge.'s version table to know what an old file means and can use Blockception's descriptions to validate the current contract. Neither validates cross-file references; bridge. does that through its "lightning cache" index of identifiers and events.
4. Parity verification precedents
- Pumpkin compares generated chunks against vanilla captures.
crates/pumpkin-world/src/generation/proto_chunk_test/(2,014 lines) reads 29*.chunkdumps fromassets/tests/(22 MB; names such asnoise_no_blend_no_beard_0_0.chunk,nether_carvers_no_blend_no_beard_7_4.chunk) and compares the block state id at every position (dumps.rs:44-80), withallowed_mismatches = 6000for the noise stage (dumps.rs:31) and separate surface, biome and carver checks;stage_parity.rs:326-347compares per-section FNV hashes of state ids againststage_hashes.json;density_function_tests.jsonandmulti_noise_sample_*.jsonpin sampled values. The captures come from the external Extractor mod; no in-repo code regenerates them. - Allay checks the palette in both directions and the data coverage of every state.
server/src/test/java/org/allaymc/server/block/BlockStateHashTest.java:56-65asserts every hash inblock_states.jsonexists inRegistries.BLOCK_STATE_PALETTEand everyminecraft:palette entry exists in the data;BlockStateDataTest.java:101-118walks everyBlockIdand every state and fails if any state hash lacks a data row. 123 test files in total. - Dragonfly makes parity a startup invariant:
RegisterBlockpanics on a state the palette lacks or a duplicate,Finalizepanics on hash collisions or a missingminecraft:air,RegisterItempanics on a name missing fromvanilla_items.nbt, and the creative menu and recipe loaders silently drop unregistered content. Its 10_test.gofiles cover redstone scenarios and break info, not vanilla comparison. - PowerNukkitX logs rather than fails:
BlockStateRegistry.java:30-58reports "failed to find block state" or "BlockState <hash> was not <name>" for every palette entry at init.BlockDefinitionParityTest.java:55-85asserts each block's no-arg constructor agrees with registry construction;ItemDefinitionParityTestspot-checks numbers (diamond sword damage 7). - CloudburstMC/Server throws only on duplicate
blockStateHashrows indata/blocks.json(BlockPropertyData.java:28-70). - minecraft-data validates every file against a JSON schema with Ajv (
tools/js/test/test.js) and checks that Bedrock state ranges tile the palette (bedrock_block_states.js);bedrock-ci.ymlinstalls the PR's data into abedrock-protocolcheckout and runs its tests against real BDS builds (14-minute timeout). - bedrock-protocol boots BDS per version in CI, dumps packets, then replays them through its own server implementation (
test/internal.test.js,tools/genPacketDumps.js); its helper bot detects new versions by booting BDS and reading the pong. prismarine-chunk re-encodes capturedlevel_chunkandsubchunkpackets and asserts equal blob hashes. flying-squid drives mineflayer bots through dig, place and container flows. - None of the Bedrock servers compares behavior against a running BDS. The earlier note (section 7) showed BDS boots on a hosted runner in 2 to 3 seconds, and bedrock-protocol proves 19 versions per CI run is affordable. The unexploited check is a side-by-side run: the same Script API pass that dumps
BlockStates.getAll(),ItemTypes.getAll()plusgetComponents(), andEntityTypes.getAll()from BDS, diffed against what this server loaded, plus packet replay ofStartGame,ItemRegistry,BiomeDefinitionListandCraftingDatapayloads.
5. Recommendation for this repository
Pattern (c), definitions plus component lists keyed by Mojang's component names, implemented with (b)'s one-base-behavior-per-archetype idea and Dragonfly's load-time flattening. Reasons:
- Bedrock's own files are already in this shape:
components,component_groups,eventsandpermutationsfor entities and the 98 data-driven blocks,componentsfor 74 items (earlier note, section 3). Mojang's extension model for everything else is the 15 block and 7 item hooks with list-order dispatch and permutation override. Mirroring it makes "component not implemented" a log line and keeps the vanilla JSON loadable without translation. - Allay proves the shape covers 1477 blocks with about 170 base behaviors, and Cloudburst proves it needs no reflection: a map from hook key to handler per type is enough. TypeScript suits that better than Java does: a definition is a plain object, a component registry is
Map<string, ComponentFactory>, and hook sets are explicit interfaces, so the dispatch tables can be built once per definition at load time (an array of handlers per hook name, in component order) and hot flags (isSolid, random tick, light filter, blast resistance) can go into typed arrays indexed by runtime id, as Dragonfly'sFinalizedoes. - The earlier note's section 8 lists why the current class-per-thing code resists data: constructors need legacy ids, properties are overridable methods, base-class initializers call them during construction, and
instanceofchecks onItemandMob. Pattern (c) removes all four, because a block or item is a definition reference plus state, not an instance of a class. - Entities take Mojang's JSON as is: attribute components first (
minecraft:health,minecraft:collision_box,minecraft:type_family,minecraft:loot), thenminecraft:behavior.*goals mapped by name with the JSON priority onto the existingentity/ai/goals/classes, then component groups and events as the state machine. Allay and PNX both hand-build what the JSON already says; Pumpkin shows a full goal port is 22,513 lines on the Java side, so expect the goal implementations, not the data plumbing, to be the long tail. - ECS stays optional for entities later and is not a fit for blocks or items.
Concrete pieces to reuse or copy, with license against this repository's MPL-2.0:
| Piece | Where | License | Use |
|---|---|---|---|
Registry<T> with parser, lazy entries, builtin set, tags; Holder.parser reference-or-inline; HolderSet.fromJson with replace | deepslate src/core/{Registry,Holder,HolderSet,Identifier}.ts | MIT | Dependency or copied shape for the definition registries and tag merging |
FNV-1a-32 network hash over sorted NBT, Finalize flattening into per-runtime-id flags, panic-on-unknown-state registration, creative and recipe self-trimming, custom block BlockEntry and pack builder | Dragonfly server/world/network_block_hash.go, block_registry.go:283-364, item/creative/creative.go, item/recipe/vanilla.go, server.go:398-437 | MIT | Port to TypeScript with attribution; this repo already hashes the same way in block/state/BlockRuntimeIds.ts |
BDS discovery, download, server.properties edit, Server started detection, pong probe | extremeheat/minecraft-bedrock-server src/index.js | MIT | devDependency or copied into the capture job; add the browser User-Agent the CDN needs and an explicit EULA gate, which it lacks |
| Boot BDS per version in CI, dump packets, replay through own server | bedrock-protocol test/internal.test.js, tools/genPacketDumps.js | MIT | Model for the side-by-side check in section 4 |
Per-format_version schema tables and formatVersions.json; build-time custom component expansion | bridge-core/editor-packages, bridge-core/dash-compiler | MIT | Validate loaded packs; know what an old format_version means |
| Latest-superset schemas with component defaults and caveats | Blockception/Minecraft-bedrock-json-schemas | BSD-3-Clause | Compatible with MIT given attribution; validation of vanilla and user packs |
Per-state block data rows (block_states.json shape) | AllayMC/BedrockData (CC0), Allay data/ (MIT) | CC0, MIT | Same content as the CloudburstMC/Data submodule already pinned (Apache-2.0); either is fine |
Component injection system, base-plus-add-on component split, BlockStateDataTest coverage check, generated null-guarded initializer | Allay server/.../component/, block/type/, tests | LGPL-3.0 | Design reference only; do not copy code |
Loot evaluator shape (check_condition, LootTableHandle static or disk), chunk dump parity method, codegen from datapack JSON | Pumpkin | GPL-3.0 | Design reference only |
65 ComponentType hook names and configureDoor-style binding tables | CloudburstMC/Server api/.../block/BlockComponents.java, registry/VanillaBlockBehaviors.java | LGPL-3.0 api, GPL-3.0 server | Reference for the hook vocabulary, compared against Mojang's 15 |
| Hook vocabulary and dispatch rules | Mojang Script API docs (BlockCustomComponent, ItemCustomComponent, custom components overview) | Documentation | Adopt the hook names so a server-side component and a Script API component look alike |
Avoid as a data source: minecraft-data's Bedrock game data (stops at 1.26.30, Java-derived materials and blockCollisionShapes, broken harvestTools and drops ids at 1.26.30) and prismarine-chunk for 1.26 (no implementation). Both remain useful as design references.
Open questions
ItemDurabilityComponentis documented as applying "only to data-driven items". If that holds forItemTypes.getAll()on vanilla tools, the Script API pass the earlier note proposed as its second source cannot read tool durability, anditems.json-style dumps (Allay'smaxDamage,armorValue,attackDamagefrom a hooked binary) remain the only source for 1550 items. This needs a test against BDS.- Allay never measured its reflection-based component event dispatch; whether per-object event buses cost anything at 20 ticks per second with thousands of entities is unknown. A TypeScript port would avoid the question with precomputed handler arrays, but the base-versus-add-on split still needs a rule for handler order (Mojang's list order or Allay's priority).
- Pumpkin's
allowed_mismatches = 6000tolerance for the noise stage has no documented origin. Which stage diverges and why is not written down. - Nothing on Learn documents the
world_behavior_packs.jsonarray order or how BDS resolves two packs that both carry a definition with the same identifier andmin_engine_version; the "top of stack wins" rule is stated for the client UI. A BDS experiment would settle it. - minecraft-data's npm package license and the maintenance status of extremeheat/minecraft-data-extractor were not checked; the clone's README says MIT with a caveat and has no LICENSE file.
- CloudburstMC/Server's move from
BlockBehaviorclasses toComponentTypelambdas has no written rationale in the repository; whether it stalled or is converging is visible only in commit activity (active as of 2026-10-22). - Whether an ECS for entities pays off in TypeScript depends on entity counts this server does not yet reach; no project in this set has measured it.
Reproduction
mkdir -p /tmp/bds-research-priorart/tools && cd /tmp/bds-research-priorart
for r in Pumpkin-MC/Pumpkin:pumpkin Minestom/Minestom:minestom Minestom/MinestomDataGenerator:minestom-data-generator \
valence-rs/valence:valence ferrumc-rs/ferrumc:ferrumc cuberite/cuberite:cuberite AllayMC/Allay:allay \
df-mc/dragonfly:dragonfly PowerNukkitX/PowerNukkitX:pnx CloudburstMC/Server:cloudburst CloudburstMC/Nukkit:nukkit \
PrismarineJS/minecraft-data:minecraft-data PrismarineJS/prismarine-registry:prismarine-registry \
PrismarineJS/prismarine-block:prismarine-block PrismarineJS/prismarine-item:prismarine-item \
PrismarineJS/prismarine-entity:prismarine-entity PrismarineJS/prismarine-chunk:prismarine-chunk \
PrismarineJS/flying-squid:flying-squid extremeheat/minecraft-bedrock-server:minecraft-bedrock-server \
PrismarineJS/bedrock-protocol:bedrock-protocol misode/deepslate:deepslate bridge-core/editor:bridge-editor \
Blockception/Minecraft-bedrock-json-schemas:blockception; do
git clone -q --depth 1 "https://github.com/${r%%:*}" "${r##*:}"
done
for d in */; do [ -d "$d/.git" ] && printf "%s %s\n" "$d" "$(git -C "$d" rev-parse HEAD)"; done
# Allay counts
grep -c 'BlockTypes\.[A-Z_0-9]* = ' allay/server/src/main/java/org/allaymc/server/block/type/BlockTypeInitializer.java # 1035
grep -c 'if (BlockTypes\.' allay/server/src/main/java/org/allaymc/server/block/type/BlockTypeDefaultInitializer.java # 1477
grep -o 'builder([A-Za-z]*Impl.class)' allay/server/src/main/java/org/allaymc/server/block/type/BlockTypeDefaultInitializer.java | sort -u | wc -l # 366
find allay/server/src/main/java/org/allaymc/server/block/component -name '*BaseComponentImpl.java' | wc -l # 173
grep -rn 'manager.callEvent' allay/server/src/main/java/org/allaymc/server/block/component/BlockBaseComponentImpl.java | wc -l # 10
# Pumpkin
sed -n 5,6p pumpkin/crates/pumpkin-data/build.rs
grep -c 'manager.register(' pumpkin/crates/pumpkin-core/src/block/registry.rs # 217
ls pumpkin/assets/tests | wc -l # 42
sed -n 29,32p pumpkin/crates/pumpkin-world/src/generation/proto_chunk_test/dumps.rs
# Dragonfly, Cloudburst, Minestom
sed -n 115,121p dragonfly/server/world/tick.go
grep -n 'is not registered\|already registered' dragonfly/server/world/block_registry.go
sed -n 708,716p cloudburst/server/src/main/java/org/cloudburstmc/server/registry/VanillaBlockBehaviors.java
sed -n 121,133p minestom/src/main/java/net/minestom/server/registry/RegistryData.java
# minecraft-data id-space check (script kept outside the clones, run with -I)
python3 -I /tmp/bds-research-priorart/tools/check_mcdata.py /tmp/bds-research-priorart/minecraft-dataThe check_mcdata.py script loads data/bedrock/1.26.30/{blocks,items}.json, counts harvestTools keys and integer drops that resolve to an item id, and prints the dataPaths.json entry for bedrock.1.26.51.