1.6.0
Compatibility
Changes
The whole break/shockwave pipeline now reads each Sable sub-level's
ACTUAL mass (in kg) from Sable's own MassData tracker instead of
treating every attacker as a hard-coded 2000 kg "default ship". Per
the Sable wiki, every block carries a sable:mass property (kpg);
ServerSubLevel#getMassTracker().getMass() returns the sum that the
physics pipeline itself uses for Rapier. We just read it.
This single change fixes the long-standing complaint that "a tiny flying chunk feels exactly as heavy as a 5000-block ship": below, the same 12 m/s impact now produces three very different outcomes depending on the attacker's real mass.
What is new
-
SubLevelMassResolver— single source of truth for per-sub-level mass. O(1) per call (queriesServerSubLevel#getMassTracker()), per-tick identity-keyed cache, defensive fallback todefaultAttackerMassKgon any Sable-side failure. Hard-capped atsubLevelMassHardCapKg = 1e8 kgto prevent a corrupted physics tick from making the energy budget infinite. -
Mass-aware kinetic-energy budget.
BreakResolver.shouldBreakStatewas already a kinetic-energy formula, but it was always fed 2000 kg. Now it sees the real attacker mass, so:- A 100 kg flying cobblestone at 12 m/s = 7 200 J → bounces off stone (budget 18 kJ).
- A 50 t small ship at 12 m/s = 3.6 MJ → punches stone wide open.
- A 500 t fortress at 6 m/s = 9 MJ → grinds through obsidian. The reduced-mass term µ = m1·m2/(m1+m2) means a feather-light attacker still cannot pulverize a heavy defender — the math collapses to the attacker's own KE budget.
-
Mass-aware structural-toughness gate. The "soft attacker can't pierce hard defender" rule (
attackerToughnessRatio) now has a mass relief factor: the required attacker toughness is scaled bysqrt(massToughnessReferenceKg / realMass), floored atmassToughnessReliefFloor. A 100 t crusher made of packed dirt WILL pulverize stone purely from inertia; a thrown dirt clump won't. Defaults:massToughnessReferenceKg = 4000,massToughnessReliefFloor = 0.1(so at most a 10× relief — a fully soft Jell-O sub-level can never bypass the gate entirely). -
Shockwave magnitude now uses the same real mass. The bespoke flood-fill (
computeAttackerMassUnits, O(N) up to 1500 blocks per emit) is no longer the primary path — whenuseSubLevelMass=truewe feed Sable's mass tracker ×shockwaveAvgToughnessUnits(= 120, i.e. V_STONE × 10) so all existing magnitude thresholds keep their meaning. Flood-fill remains as the fallback for the unlikely case where the mass tracker has not been built yet. Net result: shockwave emission is now O(1) per impact and reflects the real ship size, not a 6-neighbour sample of it. -
New persistent config knobs (TOML hot-reload as usual):
useSubLevelMass(bool, default true)subLevelMassHardCapKg(kg, default 1e8)massToughnessReferenceKg(kg, default 4000)massToughnessReliefFloor(×, default 0.1)shockwaveAvgToughnessUnits(units, default 120)
What is fixed
-
"Two tiny dirt blocks shouldn't break obsidian by being heavy" — was actually still happening for SHIP-vs-OBSIDIAN because the callback was hard-coding 2000 kg. With real mass, a 200 kg dirt fragment now correctly fails the gate AND fails the energy budget.
-
Heavy ships landing softly used to bounce indefinitely because every contact was rated at 2000 kg @ low speed = 144 kJ, below every threshold. They now report their real mass, so a 100 t ship setting down at 4 m/s = 800 kJ punches through dirt/wood as expected.
-
Shockwave was firing inconsistently for big builds because the flood-fill capped at
shockwaveMassFloodCap = 1500blocks underestimated ships larger than that. The real mass tracker has no such cap.
Backward compatibility
useSubLevelMass=falserestores the pre-1.6.0 behaviour exactly (hardcodeddefaultAttackerMassKg, flood-fill for shockwave).- All existing config keys preserved with the same defaults.
BreakResolver.shouldBreakState(...)signature unchanged.ShockwaveEffect.emit(level, center, energy)overload kept; a newemit(level, center, energy, attackerMassKgHint)overload is preferred by callers that already have the resolved mass.
Requirements
- Minecraft 1.21.1
- NeoForge 21.1.227+
- Sable 1.1.3+ (uses
ServerSubLevel#getMassTracker())
Credits
- Code: Xylos_Official
- Visuals: Viaquelt
- Built on top of Sable by ryanhcode
MIT License
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:s8kSCCgD:KFajaQ45"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:s8kSCCgD:KFajaQ45"
}

