Data-Driven Erosion#32
Open
Amakazor wants to merge 10 commits into
Open
Conversation
Owner
|
hi! this looks interesting. I haven't had the chance yet to look into it in detail and assess wheter this is more performant that the current implementation, but if so I'd definitely consider a merge since I do want to add seamless compat with mods that add blocks. that's something that's still a few items below in the roadmap so will take some time, but i'll keep this in mind when I cross that bridge. thank you! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Foreword
I was experimenting with creating a custom instance of TRMT for my own use, based on data-driven principles to allow for quick and painless creation of compat datapack between TRMT, BYG, Regions Unexplored and so on. It works well enough, that I decided, that it might be somewhat useful for you to take a look at this approach.
I have read the Contributor License Agreement in CLA.md and I agree to its terms
Summary
Erosion and de-erosion are now described by datapack JSON rules. Forward erosion rules form a directed acyclic graph where each edge transforms one blockstate into another. De-erosion walks that graph backwards by restoring recorded blockstate history, so convergent paths can reverse correctly without guessing where a block came from.
The system supports:
This makes compatibility mostly a data problem: another mod or datapack can add new erosion and de-erosion edges under its own namespace.
Mental Model
Think of erosion transforms as a directed acyclic graph.
Each forward erosion rule describes one edge:
For example:
Multiple sources may converge on the same target:
The system records the exact previous blockstate before each forward transform. If both paths reach
trmt:eroded_dirt, de-erosion can still restore the correct previous state for each individual block.Forward traversal is data-driven by
erosion_transforms:Backward traversal is data-driven by
deerosion_transforms:If history is empty, a de-erosion rule may use its
fallback. Fallbacks are for manually placed or legacy states. Normal erosion and de-erosion should follow recorded history.Cycles are rejected. If a loaded transform graph contains a cycle, erosion transforms are disabled and the DAG issue is reported.
File Locations
Forward erosion rules:
De-erosion rules:
Files contain a JSON array of rule objects. Rules are loaded from all namespaces, so a compat mod can add files under its own namespace:
Matching Blocks
Use
identifierfor one block:{ "identifier": "minecraft:dirt" }Use
identifiersfor multiple blocks or tags:{ "identifiers": [ "minecraft:short_grass", "minecraft:tall_grass", "#minecraft:flowers" ] }Identifiers should always be namespaced. Tags use a leading
#.Forward Erosion Rules
A forward erosion rule has a source matcher, a threshold, and an ordered operation pipeline.
{ "identifier": "minecraft:grass_block", "threshold": { "min": 2.0, "max": 4.0 }, "operations": [ { "name": "requires_config", "key": "erosion.grassEnabled" }, { "name": "next_state", "id": "trmt:eroded_grass_block", "properties": { "stage": "0" }, "property_sources": { "facing": "position" } }, { "name": "apply_state" } ] }thresholdcontrols how much traffic a tracked position needs before the rule transforms. A random value is chosen betweenminandmaxwhen the position is tracked.operationsrun in order. An operation may stop the pipeline by returning no result.Forward Operations
Supported operations:
requires_configchecks one config key:{ "name": "requires_config", "key": "erosion.dirtEnabled" }The config key must be one of the supported config paths. Unknown keys are rejected during rule evaluation.
requires_aircurrently supportsside: "top":{ "name": "requires_air", "side": "top" }next_stageincrements an integer blockstate property namedstageup tomax:{ "name": "next_stage", "max": 4 }If the current stage is already at
max, the operation leaves no proposed next state. This is commonly followed by anothernext_stateoperation.next_stateproposes a new blockstate:{ "name": "next_state", "id": "minecraft:stone_brick_stairs", "properties": { "waterlogged": "true" }, "property_sources": { "facing": "position" } }clear_if_no_stateclears or cools down tracking when no new state has been proposed.stop_trackingprevents the next cooldown entry from being written.apply_statewrites the proposed state to the world and records reversible history.break_blockdestructively breaks the matched block:{ "name": "break_block", "drop_chance": 0.1 }Vegetation and leaves currently use destructive erosion. They do not need reversible history.
Blockstate Properties
Use
propertiesfor fixed target values:Use
property_sourcesfor runtime-derived values:Supported property sources:
All property values are strings and must match the target block's property names and allowed values. Invalid properties or invalid values reject the rule during loading.
Reversible History
Forward erosion records the full current blockstate before applying the next state. A history snapshot contains:
This is intentionally generic. It is not limited to
stageorfacing, and it can restore custom properties such as:{ "age": "3", "waterlogged": "false", "variant": "mossy" }History does not store block entities or block entity NBT.
De-Erosion Rules
A de-erosion rule describes how a block can move backwards through history, how long natural de-erosion waits, which config gates apply, and what fallback to use if history is empty.
{ "identifier": "trmt:eroded_sand", "natural_config": "deErosion.sandEnabled", "item_triggers": { "minecraft:brush": { "config": "bonemealDeErosion.sandEnabled", "mode": "hold", "ticks": 20, "damage": 1, "world_event": 2005 } }, "timeout_days_by_property": { "stage": { "0": 3.0, "1": 5.0, "2": 8.0, "3": 13.0, "4": 13.0 } }, "fallback": { "id": "minecraft:sand" } }De-erosion is history-first:
natural_configcontrols manager-driven natural de-erosion. Omit it when natural de-erosion should not have a config gate. Unknown config keys are rejected during rule evaluation.Natural de-erosion scans tracked loaded positions every 200 server ticks. It does not scan the whole world for matching blocks.
De-Erosion Timeouts
Use
timeout_dayswhen all states share one timeout:{ "timeout_days": 8.0 }Use
timeout_days_by_propertywhen a property controls the timeout:{ "timeout_days_by_property": { "stage": { "0": 3.0, "1": 5.0, "2": 8.0, "3": 13.0, "4": 13.0 } } }If the block is isolated, the existing isolation timeout reduction still applies.
Item-Triggered De-Erosion
item_triggerscontrols which items can manually de-erode a matching block. Item-triggered de-erosion uses the same rule lookup and history restoration as natural de-erosion, but it ignores the natural timeout.The
configfield is optional. If present, the config key must be enabled for the item to work. Omit it when the item should not have a config gate. Unknown config keys are rejected during rule evaluation.mode: "instant"runs from normal block interaction, such as right-clicking with bone meal.mode: "hold"runs while the item is in Minecraft's active-use ticking, so it fits brush-like items. It does not make arbitrary tools holdable by itself; the item still needs to enter active use.ticksis used by hold triggers and controls how long active use must continue before a step-back is attempted.consume,damage, andworld_eventdescribe item side effects after a successful step-back.Arbitrary Item Examples
A consumable item can repair one history step on right-click:
A durability tool can repair one history step on right-click and lose durability after success:
An active-use item can repair one history step after being held on the block:
Fallback States
fallbackis used only when history is empty:Fallbacks are useful for manually placed blocks and old tracked entries that do not have history.
Entity Erosion Flow
Player, mob, and vehicle erosion now share the same erosion logic path.
Living entities provide an erosion multiplier. Entity-level vehicle logic can add passenger erosion by summing passenger multipliers. This keeps player walking, mob walking, leashed mobs, and vehicles on the same transform pipeline.
The important consequence for data rules is that the source of traffic no longer needs special transform code. Once a block position reaches its threshold, the data-driven rule decides what happens next.
Debugging
Use the debug UI to inspect tracked erosion progress and timeout information.
Use the DAG command to print the loaded transform graph:
If a cycle is found, erosion transforms are disabled and the command reports the cycle. Players joining after reload also receive a warning message when the loaded graph is cyclic.
Default Data
The default data files cover the existing built-in behavior:
These defaults are examples of the public data shape as well as the mod's built-in behavior.
Compatibility Workflow
To add a new compat path:
data/<namespace>/trmt/erosion_transforms/.identifier,identifiers, or a tag.threshold.data/<namespace>/trmt/deerosion_transforms/if the target should naturally or manually reverse./reloador restart the world, then inspect/trmt erosion-dag.For a simple one-way destructive transform, use
break_blockand no de-erosion rule.For a reversible transform, make sure the forward path eventually uses
apply_state, because that is what records history.Intentional Limits
Reversible history stores blockstates, not block entities or block entity NBT.
Natural de-erosion is manager-driven and only scans positions already tracked by the erosion manager. Existing world blocks that never went through the erosion pipeline are not globally scanned.
Datapacks can add rules, but they cannot register new blocks at reload time. Staged visual transitions for arbitrary block pairs still require existing registered intermediate blocks.
Hold item triggers only work for items that enter active-use ticking.
Conflict handling is currently simple: loaded rules are evaluated in resource order and the first matching forward rule applies.