Welcome to Kore 26.2!
Kore 26.2 is the cycle where the library stopped being JVM-only, and where most of the data-driven DSL got a serious retype.
It started on the tail of 26.1 with a Kotlin 2.4 upgrade and a multiplatform port, then turned into a long pass over worldgen and predicates: scoped builders everywhere, vanilla names everywhere, and a lot of fields fixed to actually match the JSON the game reads. On top of that, the new 26.2 content landed as it came: sulfur cubes, speleothems, geysers, and the usual batch of item component changes.
This cycle is also the most breaking one in a while. Nearly every rename here exists because the old API let you write something the game refuses, or because a global function polluted autocomplete in places it had no business being. The migration is mostly mechanical, and the examples below show the before and after.
What's new?
The big stuff this cycle was:
- Multiplatform Support:
kore,oop,helpersandbindingsnow target JVM and JS (browser + Node.js) at the same coordinates, powered by a pure-KotlinZipReader/ZipWriterand aPlatformIOabstraction (OPFS in the browser).org.jomlandkotlin-reflectare gone as dependencies. - Kotlin 2.4: no more
-Xcontext-parameterscompiler argument, andjava.util.UUIDis replaced by the stable multiplatformkotlin.uuid.Uuid. - Worldgen: density functions, surface rules, carvers, processor lists, template pools, structures, block predicates, state providers, vertical anchors and height providers all moved to scoped builders, so autocomplete only offers what is valid in each context. New Providers and Carvers pages came with it.
- Predicates: every sub-predicate now carries its vanilla
*Predicatename, serialization is checked againstvanilla-mcdoc, item sub-predicates moved to the component matchers, and score holders became a sealedScoreProvider. - 26.2 content:
sulfur_cube_archetypewith explosion, contact damage, knockback and sounds, thespeleothemrename, geyser particles, thesequence,template,weighted_random_selectorandrandom_patchconfigured features, and theinterval_selectdensity function.
Code examples
Here is what the biggest 26.2 changes look like in practice.
Multiplatform datapacks
kotlin {
jvm()
js { browser(); nodejs() }
}
// works on every target
val zipBytes: ByteArray = pack.generateZipBytes()
// JVM/Node only, real filesystem
val datapack = exploreDatapackZip(zipBytes, "uploaded_pack.zip")Scoped worldgen builders
Configured features, carvers and surface rules are built inside their own scope instead of loose global functions, so autocomplete only offers what is valid where you are.
// Before: global functions, the name is a positional argument of an outer call
configuredFeature("ore_iron", ore(size = 9, discardChanceOnAirExposure = 0.0) {
targets(target(tagMatch(BlockTags.STONE_ORE_REPLACEABLES), blockState(Blocks.IRON_ORE)))
})
configuredFeature("bamboo_feature", bamboo(probability = 0.2))
// After: one scope, one method per feature type, the name is the first parameter
configuredFeatures {
ore("ore_iron", size = 9, discardChanceOnAirExposure = 0.0) {
targets { target(blockState(Blocks.IRON_ORE)) { target = tagMatch(BlockTags.STONE_ORE_REPLACEABLES) } }
}
bamboo("bamboo_feature", probability = 0.2)
}// Before: surface rules built as nested sequence(condition(...), ...) calls
noiseSettings("overworld") {
surfaceRule = sequence(
condition(biome(Biomes.DESERT), block(Blocks.SAND)),
condition(stoneDepthFloor(offset = 0, addSurfaceDepth = false, secondaryDepthRange = 0), block(Blocks.GRASS_BLOCK)),
block(Blocks.STONE),
)
}
// After: a surfaceRules { } scope, condition() takes a block lambda
noiseSettings("overworld") {
surfaceRules {
condition(biomes(Biomes.DESERT)) { block(Blocks.SAND) }
condition(stoneDepth(Surface.FLOOR, addSurfaceDepth = false, secondaryDepthRange = 0)) { block(Blocks.GRASS_BLOCK) }
block(Blocks.STONE)
}
}// Before: carvers split between air and liquid lists on the biome
biome("my_biome") {
carvers(air = listOf(ConfiguredCarvers.CAVE), liquid = listOf(ConfiguredCarvers.CANYON))
}
// After: a configuredCarvers { } scope, and one flat carvers list on the biome
configuredCarvers {
cave("my_cave") {
probability = 0.15
y = uniformHeightProvider(aboveBottom(8), absolute(180))
replaceable(Blocks.STONE, Blocks.DIRT, Tags.Block.BASE_STONE_OVERWORLD)
}
}
biome("my_biome") {
carvers(ConfiguredCarvers.CANYON, ConfiguredCarvers.CAVE)
}// Before: positional predicates, offset shared by the whole filter
configuredFeaturesBuilder.spike("test_spike", canPlaceOn = Solid, canReplace = Solid, state = blockStateStone())
blockPredicateFilter(allOf { matchingBlockTag(tag = Tags.Block.DIRT) })
// After: scoped blocks, offset moved inside each predicate
configuredFeaturesBuilder.spike("test_spike", state = blockStateStone()) {
canPlaceOn { solid() }
canReplace { solid() }
}
blockPredicateFilter { predicate { matchingBlockTag(Tags.Block.DIRT) { offset(0, -1, 0) } } }// Before: template pool and processor list entries added through entry()/add()
templatePool("every_element") {
fallback = TemplatePools.Village.Plains.TOWN_CENTERS
entry(feature(PlacedFeatures.PILE_HAY), weight = 3, projection = Projection.TERRAIN_MATCHING)
}
// After: one flat builder method per element type, weight/projection as named params
templatePool("every_element") {
fallback = TemplatePools.Village.Plains.TOWN_CENTERS
feature(PlacedFeatures.PILE_HAY, weight = 3, projection = Projection.TERRAIN_MATCHING)
single(Structures.EndCity.BRIDGE_GENTLE_STAIRS, ProcessorLists.HOUSING, weight = 2) {
overrideLiquidSettings = LiquidSettings.IGNORE_WATERLOGGING
}
}
processorList("every_processor") {
blockRot(0.75, Blocks.STONE_BRICKS, Tags.Block.STAIRS)
capped(6, Nop)
gravity(HeightMap.OCEAN_FLOOR, offset = -1)
}World presets keyed by dimension id
Dimensions are keyed by their id instead of their type, so two dimensions can share a type, and generator builders set generator themselves.
// Before: keyed by dimension type, generator assigned by hand, one type usable only once
worldPreset("my_preset") {
dimension(DimensionTypes.OVERWORLD) {
generator = noiseGenerator(NoiseSettings.OVERWORLD, multiNoise(BiomePresets.OVERWORLD))
}
}
dp.noise("hills", firstOctave = -5, amplitudes = listOf(1.0, 0.5, 0.25))
// After: keyed by dimension id, the builder sets generator itself, amplitudes is variadic
worldPreset("my_preset") {
dimension(Dimensions.OVERWORLD, DimensionTypes.OVERWORLD) {
noiseGenerator(NoiseSettings.OVERWORLD, multiNoise(BiomePresets.OVERWORLD))
}
dimension(DimensionArgument("mining", "test"), DimensionTypes.OVERWORLD_CAVES) {
noiseGenerator(NoiseSettings.CAVES, multiNoise(BiomePresets.OVERWORLD))
}
}
dp.noise("cave/entrance") { firstOctave = -5; amplitudes(1.0, 0.5, 0.25) }
// Superflat settings get a layers { } builder, and structureOverrides takes structure set tags
flatLevelGeneratorPreset("tunnelers_dream", Items.STONE) {
settings {
layers { layer(Blocks.BEDROCK); layer(Blocks.STONE, height = 230); layer(Blocks.GRASS_BLOCK) }
structureOverrides(StructureSetTagArgument("my_structure_sets", "test"))
}
}Sulfur cubes
// Before: a single explosionFuse field
sulfurCubeArchetype("regular", items = Tags.Item.SWORDS) { explosionFuse = 80 }
// After: structured explosion, contact damage and knockback, plus sound settings
sulfurCubeArchetype("regular", Tags.Item.SWORDS, horizontalKnockbackPower = 0.4f, verticalKnockbackPower = 0.2f) {
contactDamage(amount = 3f, damageType = DamageTypes.GENERIC, attributeToSource = true)
explosion(fuse = 80, power = 3, causesFire = true)
}Migration notes
Every rename in this cycle, with the code to change on your side.
UUID becomes Uuid
The stable kotlin.uuid.Uuid is idiomatic, simpler, and multiplatform. If you only use the uuid() helpers, nothing changes.
// Before
UUID.fromString("6c0b9f1e-...")
UUID.randomUUID()
// After
Uuid.parse("6c0b9f1e-...")
Uuid.random()item(...) becomes items(...) on item predicates
The builder takes several items and matches any of them, so the plural fits. Loot table entries keep their own item(...), which drops a single item.
// Before
matchTool { item(Items.DIAMOND_PICKAXE, Items.NETHERITE_PICKAXE) }
// After
matchTool { items(Items.DIAMOND_PICKAXE, Items.NETHERITE_PICKAXE) }Sub-predicates take their vanilla name
Each one is named after the vanilla type it produces, so a name found in the wiki maps to the Kore one, and the global functions stop clashing with other builders of the same name. The loot context enum EntityType became EntityTarget to stop clashing with the EntityTypes registry.
// Before
effects { this[Effects.INVISIBILITY] = effect { amplifier = rangeOrInt(1) } }
equipment { mainHand = itemStack(Items.DIAMOND_SWORD) }
slots { this[WEAPON.MAINHAND] = itemStack(Items.DIAMOND_SWORD) }
location { block { blocks(Blocks.STONE) } }
scoreNumber("kills", EntityType.THIS)
// After
effects { this[Effects.INVISIBILITY] = mobEffectPredicate { amplifier = rangeOrInt(1) } }
equipment { mainHand = itemStackPredicate(Items.DIAMOND_SWORD) }
slots { this[WEAPON.MAINHAND] = itemStackPredicate(Items.DIAMOND_SWORD) }
location { block(Blocks.STONE) }
scoreNumber("kills", EntityTarget.THIS)timeCheck requires its clock
Vanilla needs a clock to know which time to read, so it moved from a trailing nullable argument to the mandatory first one.
// Before
timeCheck(10f..20f, clock = WorldClocks.OVERWORLD)
timeCheck(0f..6000f, period = 24000, clock = WorldClocks.THE_END)
// After
timeCheck(WorldClocks.OVERWORLD, 10f..20f)
timeCheck(WorldClocks.THE_END, 0f..6000f, period = 24000)valueCheck requires its range
A value_check without a range never matches anything, so the range is mandatory. An IntRange overload comes with it, since the game clamps both bounds to an integer anyway.
// Before
valueCheck(scoreNumber("kills", EntityType.THIS))
// After
valueCheck(scoreNumber("kills", EntityTarget.THIS), 1..10)
valueCheck(scoreNumber("kills", EntityTarget.THIS), intRange(0f, 10f))Score holders are now a sealed ScoreProvider
Two nullable parameters let you pass both or neither and get invalid JSON, so the holder is a sealed type now: contextScore for a loot context entity, fixedScore for a name.
// Before
scoreNumber("kills", target = EntityType.THIS)
scoreNumber("kills", name = "Ayfri")
// After
scoreNumber("kills", contextScore(EntityTarget.THIS))
scoreNumber("kills", fixedScore("Ayfri"))providersRange(...) becomes intRange(...)
Both built the same bound, so they are merged under intRange, which covers floats, providers, ClosedFloatingPointRange and IntRange.
// Before
limitCount(providersRange(min = constant(1f), max = constant(32f)))
// After
limitCount(intRange(min = constant(1f), max = constant(32f)))Number providers only resolve inside their scopes
constant, uniform and the others now extend IntProviderScope/FloatProviderScope, so they only show up where a provider is accepted, and the int and float versions stop being ambiguous. weightedList gets a shorter syntax at the same time.
// Before
count(weightedList {
add(weightedEntry(7, constant(1)))
add(weightedEntry(3, constant(3)))
})
// After
count(weightedList {
entry(7, constant(1))
entry(3, constant(3))
})
count(weightedList(7 to constant(1), 3 to constant(3)))HeightConstant becomes VerticalAnchor
Vertical anchors and height providers are scoped too, so they only appear where a height is expected.
// Before
heightRange(uniform(HeightConstant.aboveBottom(8), HeightConstant.belowTop(2)))
// After
heightRange(uniformHeightProvider(aboveBottom(8), belowTop(2)))equippable takes a generated EquipmentAssets entry
The asset id was typed as ModelArgument, so any model name compiled. EquipmentAssets is generated from the vanilla list instead.
// Before
equippable(EquipmentSlot.HEAD, ModelArgument("iron"))
// After
equippable(EquipmentSlot.HEAD, EquipmentAssets.IRON)Dripstone becomes speleothem
Vanilla renamed the features in 26.2, and they gained base_block, pointed_block and replaceable_blocks.
// Before
configuredFeature("cluster", dripstoneCluster(...))
configuredFeature("pointed", pointedDripstone(...))
// After
configuredFeatures {
speleothemCluster("cluster") { /* baseBlock, pointedBlock, replaceableBlocks */ }
speleothem("pointed") { /* ... */ }
}Removed, with no replacement
weird_scaled_sampler and the noise_gradient surface rule are gone from vanilla, so they are gone from Kore. noise_threshold gained an optional 3D evaluation and covers most of what noise_gradient was used for.
New builders worth knowing
// team modify <team> color reset
modify("my_team") { colorReset() }
// scatter a placed feature around its origin
configuredFeaturesBuilder.randomPatch("flowers", PlacedFeatures.FLOWER_DEFAULT) {
tries = 64
xzSpread = 5
ySpread = 2
}Changelog
Documentation
docs(commands): Clarify and expand the command docs.1d1e597docs(components): Revise thecomponentsguide to update examples, clarify predicates, and link the component matchers table.9d3cf51docs(contributing): Update the architecture guide withGeneratedSealedSerializerusage and KSP-based serializer factory details.b5cf883docs(generation): Add short KDoc to the generation module's key entry points.f8b527adocs(home): Add community project links and thejumprentry to the README list.e6b41aa5aaf9c4docs(oop): Improve the item, scoreboard, and team documentation.708af24docs(vfx-engine): Enhance theVfxEnginedocumentation with detailed explanations on shapes, coordinate spaces, and offsets.035c1b9docs(website): Update the guide content, clarify Kore usage and migration paths, add a version matrix to the Home page.0af799bdocs(worldgen): Add a dedicated Carvers page and move the carver sections out of the Biomes page.d2f3cf1docs(worldgen): Document theIntProviderScope/FloatProviderScopebuilder receivers on the Providers page, fix the staleprovidersRange/binomialsnippets in Item Modifiers and Loot Tables.6aec34bdocs(worldgen): Restructure the worldgen pages around a single end-to-end example, extract the shared providers to their own page, and condense the environment attribute reference into tables.6aa1827docs(worldgen): Rewrite the Noise & Terrain page as a full density function, noise router, and surface rule reference.38558aedocs(worldgen): Rewrite the Structures processor list and template pool sections with the scoped element builders.c403250ba6c643
New Features
feat(arguments): AddtoStringWithDecimalfor consistent decimal point formatting, update the positional, rotational, and vector conversions.03158e9feat(bindings): Add theDatapackUploadAPI for exploring and importing datapack ZIPs in-memory, with cross-platform tests.7dc0800feat(bindings): EnhanceparseReferenceto support dotted GitHub repo names.a3f00b4feat(commands): Addteam modify <team> color resetthroughcolorReset.f6c6bc5feat(datapack): IntroducefolderNameto decouple output folder names from namespaces, add tests for the folder, jar, and zip modes.8497d8bfeat(enchantments): Add thegeyser,geyser_base,geyser_plume, andgeyser_poofparticle options with unit tests.ccea6c1feat(entity-predicates): Introduce type-specificEntitySubPredicatesubclasses (CubeMob,FishingHook,Lightning,Player,Raider,Sheep) and anEntityTypeSpecificScopeto group them.987b370feat(execute): Changerun's block toFunction.() -> Unit, allowingif/forcontrol flow inside it.abc4a5cfeat(fabric): AddResourceConditionwithfabricLoadConditions, implement condition-based JSON generation.10543f8feat(generation): Add aDatapackFolderRegistrygenerator producing a datapack-folder-to-Argument-type map from the existing generator definitions.75bf9a9feat(generation): Add a pure-KotlinZipReaderandInflatefor cross-platform ZIP handling, unify the unzip logic withcommonUnzipToTempDir.2cad91afeat(generation): Generate theEnchantmentProvidersandEquipmentAssetsenums, retypeequippablearoundEquipmentAssetArgument.c2c2bc5feat(generation): Introduce platform-agnostic I/O withPlatformIO, OPFS browser support, and a pure-KotlinZipWriter, unifying file operations across JVM, JS, and browser.05e7b6afeat(helpers): Add a minimal in-house port oforg.joml(Matrix4f,Matrix3f,AxisAngle4f,Quaternionf,Vector3f,JomlMath) for display transformation math.0265cd6feat(item-components): Add theSulfurCubeContentComponent, with tests and documentation.8afcac3feat(selector-arguments): AddfromStringto parse selectors back from text, with deserialization logic and advanced selector support.6feb45ffeat(sulfur-cube-archetype): Add thesulfur_cube_archetypedata-driven registry with attribute modifiers and buoyancy.b2e9a3dfeat(sulfur-cube-archetype): ReplaceexplosionFusewith structuredexplosion,contactDamage, and knockback powers, then add hit and push sound settings withpushSoundCooldownandpushSoundImpulseThreshold.03233a142a9fbbdf98bdffeat(test-environments): Add thedifficultytest environment with unit tests.961bbe7feat(vfx-engine): Add coordinate space support todrawShape, enhanceVfxShapewithpositionTypeandorigin.2a5a916feat(worldgen): Accept a block ID or list of IDs, in addition to a tag, for the geode, root system, vegetation patch, dimension type infiniburn, and protected blocks replaceable fields.7349b97feat(worldgen): Add thesequenceandtemplateconfigured feature types.53eb47efeat(worldgen): Add theweighted_random_selectorconfigured feature, pluslevelTestDistanceandmaxLevelDeviationonroot_system.bf4163cf9fdad8feat(worldgen): Add theinterval_selectdensity function and thematching_biomesblock predicate, removeweird_scaled_sampler, make themultiface_growthblock and the treebelow_trunk_providermandatory.af33acffeat(worldgen): Add optional 3D evaluation tonoise_thresholdand remove the obsoletenoise_gradientsurface rule.d25fd01feat(worldgen): IntroduceIntProviderScope/FloatProviderScopeas builder receivers across placed features, configured features, dimension types, enchantment providers, carvers, and processors, and add therandom_patchconfigured feature.779217cfeat(worldgen): Enhance theLakeconfiguration with thecanPlaceFeature,canReplaceWithAirOrFluid, andcanReplaceWithBarrierblock predicates.dccef30feat(worldgen): Rename thedripstone_cluster/pointed_dripstoneconfigured features tospeleothem_cluster/speleothem, add thebase_block/pointed_block/replaceable_blocksfields, and addreplaceable_blockstolarge_dripstone.9a56efe
Bug Fixes
fix(bindings): Fix the illegal interface property initializers and unsafe identifiers left in the Resources and Tags generators.53d8836fix(chat-components): Simplify the serialization logic forextrafields and enhance the JSON/NBT handling in chat components.0aee14b34d72bb400f99ffix(commands-execute): Correct thetargetArglogic to handleUUIDArgumentequality with a self-check, and optimize the selector argument handling.59c6ecafix(components): Add the missingsaddleequipment slot.b5df734fix(datapack): Setpack_formatto an integer only and fix its default value.b613e04fix(enchantments): Write the providers toenchantment_providerinstead oftrades, and allow any file name and sub-folder.7d649e9fix(helpers): RewritefromAxisAngleto usesin/costrigonometric calculations instead of instantiating aQuaternionf.c25bcdffix(item-components): Changefood'snutritionfield fromFloattoInt, makeuse_cooldown'scooldown_groupoptional, and remove the invalidcontact_cooldown_ticksfield fromkinetic_weapon.d47a49221751921de41eafix(item-components): Renameattack_range'smin_range/max_rangetomin_reach/max_reach,blocks_attacks'sdisable_soundtodisabled_sound, andfirework_explosion'shas_flickertohas_twinkle.f6ef4da26bf1b2415971bfix(item-components): Serialize theSulfurCubeContentComponentas an inlined item id instead of a nested object.65db924fix(item-predicates): Add support for partial and negated components, enhanceclearPredicate, and simplify thesetPartialandnegatelogic.3e7804dfix(pack): Fix thepackFormatwarning wrongly reported as outside themin_format/max_formatrange when it shares their major version.b458689fix(predicates): Rename the mob effectambiantfield toambient, and write theperiodic_tickandtype_specific/cube_mobsub-predicate keys vanilla expects.9a20f9bf3b3204fix(serializers): Serialize and deserialize half-open bounds inFloatRangeOrFloatJsonSerializerinstead of throwing.2e273bffix(worldgen): Fix thenoiseandsplinedensity functions and thestone_depthsurface type to match the vanilla JSON, make the noise settings tests exhaustive.d09d92ffix(worldgen): Rename the misspelled biome spawner categories toambient,axolotls, andwaterAmbient, and add the missingmiscone.626e421c4946a4fix(worldgen): Replace the ocean ruintypefield bybiomeTemp, and default the jigsawsizeto1instead of the out-of-range0.635cc660e9bd53fix(worldgen): Return aConfiguredStructureArgumentfrom every configured structure builder so structure sets accept them.fab9d5c
Performance improvements
perf(arguments): Scope theArgumentinterface classpath scan to the arguments packages and parallelize theClass.forNamelookups.00bfcdeperf(datapack): Cache thejsonEncoderinstead of rebuilding aJsonconfig on every access.7441a97perf(item-components): Encode component values directly instead of round-tripping through aJsonElement/NbtTagtree, and skip the redundant encode-to-element pass inComponentsScope.asJson/asNbt.de3a95ef69791c
Refactors
refactor(uuid)!: Replacejava.util.UUIDwithkotlin.uuid.Uuidacross modules, updating the related methods and constructors.refactor(components): Move the item sub-predicates tocomponents/matchersasDataComponentPredicateandEnchantmentPredicate, renameitem(...)toitems(...), and add the missingvillager/variantmatcher.72b3a6brefactor(density-functions): Add thedensityFunctionsbuilder with per-type block syntax, fix thesnakeCaseacronym and digit boundaries.dfe5c62refactor(enchantments): Share the effect builders through scopes, fix theexplode,play_sound, andcrossbow_charging_soundscodecs, and complete the effect components.9a2be54refactor(predicates): Rename the sub-predicates to their vanilla*Predicatenames, fix their serialization againstvanilla-mcdoc, and requireclockontimeCheck.111e1a1refactor(predicates): Split the score targets into aScoreProvidersealed type, require ranges onvalueCheck, and add theIntRangeoverload.1674e14refactor(trade-sets): EnhanceTradeSetwith new builder methods for trade amounts and sequences, with tests for empty and single trades.892f75erefactor(worldgen): Scope the configured carver builders toConfiguredCarversScope, add the missingnether_cavetype, fix the vanilla defaults and the flat biomecarverslist.f68c443refactor(worldgen): Scope the surface rule builders toSurfaceRulesScope, replaceentry()withstate/empty, and collapse the single-rule conditions.1068a96refactor(worldgen): Scope the processor list builders toProcessorsScope, fix theblackstone_replacename and the vanilla defaults.0ddc09crefactor(worldgen): Scope the template pool builders toPoolEntriesScopeandPoolElementsScope, fix the vanilla defaults, and add thelegacySingleliquid settings.87c932crefactor(worldgen): Scope the block predicate, block state provider, and rule test builders, replaceoffsetwith a per-predicateoffset { }, addunobstructedandtargets { }on the ore-like features, and fix thenoise_threshold_provider,randomized_int_state_provider, andtag_matchnaming.43d05a9cf5c3c8872ab1crefactor(worldgen): RenameHeightConstanttoVerticalAnchor, scope the anchor and height provider builders, and document them with tests.e5ebca4refactor(worldgen): Key the world preset dimensions by their id instead of their type, make every dimension generator builder setgenerator, remove the obsoletenaturaldimension type field, and add alayers { }builder for the superflat settings.e8ca92c41c83f799f997ebed0900refactor(worldgen): Replace the list-based amplitude and octave methods with a unifiedamplitudes(...).2de880crefactor(worldgen): Retype the structures builder around aStructuresScope, fix the mineshaft, shipwreck, spawn override, and generation step keys, and document every type with tests.6a0795f
Full changelog: https://github.com/Ayfri/Kore/compare/v2.6.1-26.1..v2.13.1-26.2-rc-2