Real Sub-Level Mass
Curse Maven Snippet
What's new
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 (queries `ServerSubLevel#getMassTracker()`),
per-tick identity-keyed cache, defensive fallback to
`defaultAttackerMassKg` on any Sable-side failure. Hard-capped at
`subLevelMassHardCapKg = 1e8 kg` to prevent a corrupted physics
tick from making the energy budget infinite.
- Mass-aware kinetic-energy budget. `BreakResolver.shouldBreakState`
was 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
by `sqrt(massToughnessReferenceKg / realMass)`, floored at
`massToughnessReliefFloor`. 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 — when `useSubLevelMass=true`
we 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 = 1500` blocks
underestimated ships larger than that. The real mass tracker has
no such cap.
Backward compatibility
- `useSubLevelMass=false` restores the pre-1.6.0 behaviour exactly
(hardcoded `defaultAttackerMassKg`, 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
new `emit(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
This mod has no additional files

