
Sable: Destructive
Sable: Destructive makes Sable physics blocks actually break. Real kinetic energy, inertial penetration, density-aware self-damage, radial shockwaves on heavy hits — fast hammers dig deep into dirt, soft hammers shatter against stone, big drops radiate.
Список изменений
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
