Sable exposes a set of Java interfaces that your blocks and block entities can implement to participate in the sub-level physics system. Each interface targets a specific capability — from per-physics-tick callbacks on block entities to aerodynamic lift contributions and custom collision geometry. Implement only the interfaces relevant to your block’s behavior; unused interfaces carry no overhead.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ryanhcode/sable/llms.txt
Use this file to discover all available pages before exploring further.
BlockEntitySubLevelActor
Implement BlockEntitySubLevelActor on your BlockEntity subclass to receive tick callbacks while that block entity is mounted on a sub-level. All methods have default no-op implementations, so you only need to override what you use.
| Method | Called when |
|---|---|
sable$tick(ServerSubLevel) | Once per server game tick while on a sub-level |
sable$physicsTick(ServerSubLevel, RigidBodyHandle, double) | Once per physics substep (may be multiple per game tick) |
sable$getLoadingDependencies() | When the system resolves which sub-levels load/unload together |
sable$getConnectionDependencies() | When the system resolves connected sub-levels; used as the default for loading dependencies |
Physics logic that must influence the simulation — such as applying forces — should go in
sable$physicsTick, not sable$tick. Multiple physics substeps run per game tick, so sable$tick fires less frequently and too late to affect physics state for that substep.sable$getLoadingDependencies() delegates to sable$getConnectionDependencies() by default. Override sable$getConnectionDependencies() to declare which other sub-levels are “connected” to this one; both loading and connection logic will use it automatically unless you override sable$getLoadingDependencies() separately.
BlockSubLevelAssemblyListener
Implement BlockSubLevelAssemblyListener on your Block subclass to be notified when SubLevelAssemblyHelper moves a block of this type during sub-level assembly. This is useful for transferring state, updating references, or triggering side effects at assembly time.
| Method | Called when |
|---|---|
beforeMove(originLevel, resultingLevel, newState, oldPos, newPos) | Before the block is placed in the destination level |
afterMove(originLevel, resultingLevel, newState, oldPos, newPos) | After the block is placed; the original block has not yet been removed |
beforeMove has a default no-op implementation. afterMove is abstract and must be implemented.
BlockSubLevelCollisionShape
Implement BlockSubLevelCollisionShape on your Block subclass to provide a collision shape that differs from the block’s standard getCollisionShape. The shape returned here is used when baking the rigid body geometry for the sub-level, not for normal world collision.
BlockSubLevelLiftProvider
Implement BlockSubLevelLiftProvider on your Block subclass to contribute aerodynamic lift and drag forces to the sub-level during each physics substep. The interface includes a default implementation of sable$contributeLiftAndDrag that handles the full lift and drag calculation; in most cases you only need to provide the three scalar values and the surface normal.
| Method | Purpose |
|---|---|
sable$getNormal(BlockState) | The facing direction of this lift surface (required) |
sable$getParallelDragScalar() | Drag coefficient along the normal (default 0.75) |
sable$getDirectionlessDragScalar() | Omnidirectional drag coefficient (default ~0.0689) |
sable$getLiftScalar() | Lift force coefficient (default 0.475) |
sable$contributeLiftAndDrag(...) | Full physics callback; override to replace the built-in calculation |
The default values for
sable$getDirectionlessDragScalar are tuned to prevent exponential velocity gain relative to the default sable$getParallelDragScalar and sable$getLiftScalar. If you change either scalar, recalculate the directionless drag minimum as (-k1 + sqrt(k1² + k2²)) / 2.BlockSubLevelDynamicCollider
Implement BlockSubLevelDynamicCollider on your Block subclass to provide a collider that can change as the block state changes. Dynamic colliders are rebuilt when the block state is updated on the sub-level, rather than being baked once at assembly time.
BlockSubLevelCustomCenterOfMass
Implement BlockSubLevelCustomCenterOfMass on your Block subclass to override the center-of-mass contribution this block makes to the sub-level’s aggregate center of mass. The returned vector is relative to the lower corner of the block.
BlockWithSubLevelCollisionCallback
Implement BlockWithSubLevelCollisionCallback on your Block subclass to receive a callback when the sub-level this block is part of collides with something. The interface provides a static sable$getCallback(BlockState) helper that also returns a FragileBlockCallback for block states that carry the FRAGILE physics property.
Propeller interfaces
Two interfaces in thedev.ryanhcode.sable.api.block.propeller package handle propeller-style thrust blocks.
BlockEntityPropeller
BlockEntityPropeller defines the physical properties of a propeller: its facing direction, airflow speed, raw thrust, and whether it is currently active. The interface includes default helpers for computing air-pressure-adjusted scaled thrust and airflow-efficiency scaling.
| Method | Returns |
|---|---|
getBlockDirection() | The Direction the propeller faces |
getAirflow() | Airflow in m/s |
getThrust() | Raw thrust in pN |
isActive() | Whether to compute and apply thrust |
getScaledThrust() | Thrust adjusted for airflow efficiency and air pressure |
BlockEntitySubLevelPropellerActor
BlockEntitySubLevelPropellerActor extends BlockEntitySubLevelActor and wires a BlockEntityPropeller into the physics tick. Implement both interfaces on the same block entity class to get propulsion out of the box.
BlockEntitySubLevelPropellerActor automatically calls applyForces each physics substep when isActive() returns true, queuing the scaled thrust impulse into the sub-level’s PROPULSION force group.