Skip to content

Feat: Added partial gravity - #355

Open
luizinhoh2o1 wants to merge 22 commits into
ferrumc-rs:rework/v2-skeletonfrom
luizinhoh2o1:feat/entity-physics
Open

luizinhoh2o1 wants to merge 22 commits into
ferrumc-rs:rework/v2-skeletonfrom
luizinhoh2o1:feat/entity-physics

Conversation

@luizinhoh2o1

Copy link
Copy Markdown

I implemented gravity for falling blocks (sand, gravel, anvil, etc). Some debts remain, depending on other systems like item drops, pistons, and fluids.

Gravity blocks now fall and self-destruct in the right cases, like vanilla. Stacks collapse and re-stack correctly, and collision is tracked through the whole fall.

Landing rule

Follows vanilla: a falling block breaks if the cell it would land in isn't one it can occupy. Two cases:

  1. Thin objects in the resting cell (torch, rail, redstone, sign, sapling, flower, plate, button, lever). The sand passes through and breaks.
  2. Solid blocks shorter than a full cube it lands on top of (slab, soul sand, farmland, chest, cake, dripstone, and many more). It breaks instead of settling.

For the second case I stopped guessing by name and made it data-driven: I vendored the real per-block collision heights (derived from PrismarineJS block collision shapes) into the registry and read the collision top per state. So a bottom slab breaks, a top or double slab settles, stairs and fences settle, all automatically, with no block list to maintain.

Water and lava are replaced. The check looks only at the resting cell and the support below, so a side-wall torch off the fall path doesn't interfere.

Placing: same rule reversed. A gravity block placed on any real object stays put; it falls only over air or a replaceable block, and breaks only if it falls onto the object.

Blocks falling into the void are removed at layer -128.

Technical debts

  • Broken sand disappears instead of dropping an item (no item entities yet).
  • The collision height data is from 1.21.4 while blocks are 1.21.8, so about 10 newer blocks fall back to the coarse box flag. Collision shapes are stable across 1.21.x.
  • The sweep still grounds a faller at a full-cube height, so its exact rest position ignores partial shapes even though the break/settle outcome is correct. A full per-shape collision sweep is the next step.
  • The "replaceable" set is still keyed by name (block data has no such flag), covering the known vanilla set.
  • Item frames and paintings aren't destroyed on landing (no entity system yet).
  • Replaced fluids don't flow back, and concrete powder doesn't set in water (need fluid sim).
  • Scaffolding and landing damage (anvil, dripstone) are left out.

All covered by tests, passing formatting, lint, and audit.

ezgif-5f4d7702bdf23980.mp4

…cate

Per-category gravity magnitudes (living, item, arrow, thrown) and vertical
air drag in a new physics module. Terminal velocity emerges from the drag,
not a hardcoded clamp. Adds is_gravity_affected for the vanilla FallingBlock
set (sands, gravels, concrete powders, anvils, dragon egg, pointed
dripstone). Re-exports the constants from the crate root.
Classified error for when a shard's per-shard entity id counter runs out of
the i32 space. Maps to ServerError::Internal, mirroring the existing
TickOverflow handling.
GameInput SpawnEntity and DespawnEntity for server-driven non-player
entities, and GameOutput EntitySpawned, EntityMoved, EntityDespawned for
the session layer to map onto the wire packets.
Deterministic non-player entity store on SimShard (ordered BTreeMap,
monotonic i32 id source). Per-tick step applies per-category gravity,
integrates velocity, resolves vertical ground collision (sets on_ground,
re-checks the floor each tick), and applies air drag. Falling blocks: an
unsupported gravity block converts to a falling entity on place or break,
falls, lands on a solid block, restores its block, and despawns, including
whole-column collapse. Entities that fall past y -128 are despawned instead
of falling forever. Integration tests cover store mechanics and bit-exact
determinism.
Scope, status, and remaining work for simulated gravity: done steps
(entity store, spawn/despawn, physics step, ground collision, falling
blocks) and pending consumers (dropped items, projectiles, mobs, player).
Falling-block gravity now works end-to-end on the shard (place/break
trigger, column collapse, void despawn). Notes the current limit: the
falling entity is not yet drawn client-side (session wiring pending), so
blocks relocate without a fall animation and other gravity entities stay
unimplemented.
Adds a public SpawnedEntityKind (Simple, FallingBlock{block}) and threads it
on GameOutput::EntitySpawned so the session can pick the wire entity type and
type-specific data without querying the shard. Falling-block conversions carry
the removed block-state; the physics step stays type-agnostic.
Translates the shard's EntitySpawned/EntityMoved/EntityDespawned outputs into
clientbound packets so a falling block visibly falls on real clients. Spawns a
minecraft:falling_block (entity type 49, pinned against the same 1.21.8/proto
772 registry as the player id) carrying its block-state in the data field,
broadcasts per-tick position updates, and removes it on landing. Entities get a
network id from the shared player/entity counter so wire ids never collide;
broadcasts are scoped to viewers in view distance and re-spawn a viewer that
enters range mid-fall. Adds a deterministic per-entity UUID and the uuid dep.
Falling blocks now animate end-to-end: the session draws the fall via
SpawnEntity/move/RemoveEntities. Updates FEATURES and the sim roadmap; notes
that non-block gravity entities still lack a spawner.
Replaces the point + destination-cell collision with a per-axis swept AABB
(sweep_move + clip_x/y/z). Each entity has a box (entity_dimensions) that is
swept the full tick and clamped against every solid block along its path, one
axis at a time (Y, X, Z), so a fast mover near terminal velocity no longer
tunnels through a one-block floor and horizontal collision (walls, future
piston-pushed blocks and projectiles) resolves too. apply_entity_physics now
processes entities one at a time in ascending id order, writing a landed
falling block back to the world immediately so a higher entity in a collapsing
column sees it as solid and re-stacks correctly. Adds a tunnel regression test
and a fixed-support test at the cell centre.
A falling block whose resting cell (directly above the support) already holds a
non-full object (torch, pressure plate, button, lever, rail, redstone) breaks
instead of settling, matching vanilla. Adds breaks_falling_block predicate and
checks the resting cell only, so a wall torch on a side block never breaks a
block falling down an adjacent column. The break despawns the entity now (no
one-tick overlap needed since no block is drawn). Item drop on break is deferred
until item entities exist; the object set is name-keyed pending a registry
replaceable flag. Tests cover break-on-torch, side-torch-no-break, and the
predicate.
Removes the one-tick deferred despawn (the EntityState.landed flag and its
handling): a landed falling block now restores its block and despawns in the
same tick, block change ordered first. Simplifies the physics step and the
entity state. Updates the landing test to assert the same-tick pair.
…t stays

A placed gravity block now falls only when the block below is air or replaceable
(is_replaceable: fluids and soft growth), matching vanilla's !FallingBlock::isFree
instead of requiring a full cube. So a block placed on redstone, a torch, a slab,
or a fence rests instead of falling; it still breaks when it *lands* on such a
block. Adds is_replaceable + is_falling_support (distinct from the is_solid_block
collision test the sweep uses). Tests cover placing on redstone and the predicates.
…redicates

is_solid_block and is_falling_support both fetched the block-state and short-
circuited on a non-resident chunk; that read is now a single block_state_at
free function. No behaviour change.
…t a fixed list

Replaces the enumerated breaks_falling_block set with the vanilla rule: a falling
block breaks when its resting cell holds a non-air, non-replaceable block, and a
placed block is supported by the same. Both key off one is_replaceable set (the
vanilla free set: fluids, grass/ferns, seagrass, fire, snow layer, vines, roots)
via is_non_replaceable_block, so torches, saplings, signs, flowers, crops, plates,
buttons, levers, rails, and redstone all break a faller without enumerating each.
Removes breaks_falling_block. Test covers ten object types.
…ore sequence

Hoists the sweep broadphase Vec to a tick-local reused across every entity so the
physics step allocates once per tick instead of once per airborne entity. Adds an
early return when the shard owns no entities, avoiding the id-snapshot allocation.
Replaces the magic 0 sequence on a landed falling block's restore with a named
NO_CLIENT_SEQUENCE constant.
A falling block that lands on a solid block shorter than a full cube (slab, soul
sand, farmland, chest, cake, and similar) now breaks instead of settling on top,
matching vanilla. The vendored blocks.json only records a coarse boundingBox with
no per-shape collision height, so the sweep treats every block-box as a full cube
and stops the faller on top; a name-keyed is_partial_solid_support set plus a slab
type decode (slab_is_double, so a faller settles on a double slab and breaks on a
single) restore the correct outcome. Empty-box objects (torch, rail) were already
covered by the resting-cell test. Stairs and partials outside the set remain a gap.
…yst growth

Pointed dripstone and amethyst clusters/buds carry a coarse block bounding box in
the vendored data but have a pointed, non-full collision shape, so a falling block
lands on top and settled instead of breaking. Adds them to is_partial_solid_support
so a faller that lands on a stalagmite or amethyst cluster breaks, matching vanilla.
Adds more blocks a falling block breaks on when it lands atop them, all carrying a
coarse block bounding box in the vendored data but a collision shape below a full
cube: redstone repeater and comparator, calibrated sculk sensor, sea pickle, flower
pot, lily pad, lanterns, end and lightning rods, turtle and sniffer eggs, pitcher
crop, bamboo, floor mob heads and skulls, and every carpet, candle, and bed by
suffix. Ambiguous or wall-mounted shapes (stairs, dripleaf, chorus, ladder, iron
bars, wall heads) stay out pending real per-block collision shape data.
…lision_top_y

Adds data/block_collision_heights.json (derived from PrismarineJS blockCollisionShapes
1.21.4: the max collision-box Y per block, per state where it varies) and generates a
per-state collision_top field on BlockMetadata from it in build.rs. Exposes
collision_top_y(state_id) returning the top height in blocks: 0.0 no collision, >=1.0 a
full-height top, 0.0<h<1.0 a non-full support. A block absent from the derived file
falls back to the coarse boundingBox. This gives the simulation an accurate, per-state
collision height instead of the coarse block/empty flag.
…op name sets

Replaces the name-keyed is_partial_solid_support set and the slab type decode with a
data-driven breaks_on_support that reads the registry's per-state collision_top_y: a
faller landing on a support whose collision top is 0<h<1 breaks, and settles on a full
cube (h>=1). This makes the break exact for every block with no list to maintain, and
fixes per-state cases the name set could not (a bottom slab breaks, a top or double
slab settles, stairs settle). Empty-collision objects are still handled by the
resting-cell test. Removes is_partial_solid_support and slab_is_double.
…the data

Makes the derived per-state collision heights reproducible and provenance-tracked.
Adds scripts/gen_collision_heights.py, which reduces the pinned PrismarineJS
blockCollisionShapes to one top height per state. Regenerates the data from the exact
pinned commit's 1.21.8 snapshot (was 1.21.4), so all 1105 blocks are covered with no
boundingBox fallback. Moves the file beside the checksummed fixtures and registers it
in manifest.toml with sha256 and byte length, and points the build script at that
single provenance-tracked copy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant