Skip to content

Sync fork to upstream and land surface-relative entity orientation - #1

Open
minerguy341 wants to merge 17 commits into
mainfrom
claude/fork-sync-upstream-fixes-t0v8h4
Open

Sync fork to upstream and land surface-relative entity orientation#1
minerguy341 wants to merge 17 commits into
mainfrom
claude/fork-sync-upstream-fixes-t0v8h4

Conversation

@minerguy341

@minerguy341 minerguy341 commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Brings the fork up to ryanhcode/sable@main (76b67ea) and rebases the surface-orientation work onto it as a reviewable history.

Why

main was 12 commits / 37 days behind upstream (v2.0.3 → v2.0.4), and the feature work sat on feature/entity-surface-orientation, whose history was not in a shape that could be offered upstream: it contained a WIP commit, an abandoned implementation of the force model that was added and then reverted (BlockMixin is touched twice and ends net-zero), and a per-tick debug logging block.

The code delta here is byte-for-byte identical to feature/entity-surface-orientation, with exactly one exception: the [collide dbg] logging block is removed. Verified by diffing the two deltas against their respective bases.

What changes

Upstream, EntitySubLevelUtil.getCustomEntityOrientation is a stub returning null, so entities on a rotated sub-level stay world-upright — a player on a tilted deck stands vertical regardless of the surface. Filling that stub in is what makes surface orientation live; the rendering, shadow, camera, collision-OBB, jumping and effect plumbing is already wired to it.

Four commits, each independently reviewable:

Commit Change
ce976eb Re-orient the entity collision box per sub-level and substep
96609c2 Implement surface-relative orientation, 1°–45°
2c6c0a2 Keep surface-oriented entities stable on tilted ground
a10074d Make picking and entity lighting respect surface orientation

ce976eb is a standalone bug fix and does not depend on the rest. The OBB yaw was read from subLevelPose before that variable was assigned for the current substep, so the box was oriented to the previous substep's pose — or to a different sub-level entirely when several are loaded. In game: entities catch on walls and doorframes of rotated sub-levels that their correctly-oriented box should clear. This bites any rotated sub-level whether or not surface orientation is enabled.

96609c2 decomposes the tracked sub-level's interpolated orientation swing-twist about world Y, discarding the twist so a deck rotating underfoot does not drag the entity's heading with it. Gated to 1°–45°: below, treated as flat with vanilla behaviour; above, the entity stays upright rather than being pitched onto its side.

2c6c0a2 covers the three ways a tilted floor breaks assumptions that only hold for horizontal ground:

  • Grounded on a tilt, world-frame gravity leaves a ~g*sin(tilt) tangential remainder each tick that vanilla never absorbs, so entities creep downhill with no input. The press is aimed along the surface normal instead — simply zeroing gravity breaks contact, since the constant press is what keeps ground detection engaged. Jump detection uses velocity along the surface normal, not world Y, because walking uphill has positive world-Y and would otherwise be misread as a jump.
  • Position was reconstructed by subtracting half the body height along entityUp, which is not the inverse of how entityBoundsCenter was derived; under a custom orientation that displaces the entity laterally by ~eyeHeight*sin(tilt) every tick — a phantom conveyor on any tilted surface.
  • Vanilla's pose check tests the upright world-aligned box, spuriously forcing the crawling pose in doorways. Physical obstruction is still enforced by the oriented collision.

a10074d counter-rotates the pick ray into the entity's frame about its feet pivot so aiming at a tilted model actually hits it, and fixes two rendering faults: the light probe now samples from the oriented body center (the tilted eye lever could lean it into adjacent geometry and render the entity black), and sky-light darkening from a sub-level now requires the probe to land in that plot's air, so a neighbouring contraption whose bounds merely overlap the entity can no longer min() it to black.

4d276d6 archives the removed debug ledger under .dev/debug-snippets/ with its insertion point and field meanings, so it can be dropped back in when this work resumes.

Review notes

  • ProjectileUtil injector interaction. entity_interaction.ProjectileUtilMixin already @Redirects the same AABB.clip call sites in the same two getEntityHitResult overloads that the new entity_rotations_and_riding.ProjectileUtilMixin targets with @WrapOperation. The two demonstrably coexist at runtime — the NPE fixed in the original branch was observed on the render thread, so both applied — but the composition order is priority-dependent and worth a look. The pre-existing redirect maps the ray between sub-level frames for cross-sub-level picking, while the new wrapper counter-rotates around a world-frame pivot; if the wrapper runs inside the redirect it would be counter-rotating already-local coordinates. Untested for the cross-sub-level case specifically.
  • Sneak camera dips world-vertical while surface-oriented. Known and unfixed; it needs to integrate with Sable's camera pipeline rather than wrap Camera.setup, which is what desynced two earlier attempts. Not addressed here.
  • No in-game validation was run for this rebase. The delta is provably identical to the branch that was play-tested, but the rebase moves it onto 12 commits of upstream change it has never run against.

Not an upstream PR

This targets the fork's own main. A PR to ryanhcode/sable could not be opened from this session, which is scoped to the minerguy341 owner. Note also that upstream landed 76b67ea ("Further clarify the CLA") two days ago, and the project is PolyForm Shield 1.0.0 — worth reading before offering ce976eb upstream on its own, which is the piece most likely to be accepted independently.


🤖 Generated with Claude Code

https://claude.ai/code/session_01NBzRL3ez8kpd7KjWMyb9HG


Generated by Claude Code


Note

Cursor Bugbot is generating a summary for commit 4d276d6. Configure here.

BeeIsYou and others added 17 commits July 22, 2026 21:46
Mostly inconsequential except for some heavy-use methods such as in
WorldBorder or ServerLevel/ClientLevel
Instead of checking the current actor origin against the current block being broken, we check the current actor origin against the origin when it started breaking the block.
This ensures that no matter how large of an area the actor is breaking (IE. rollers, drills from addons that break in an absurd 10x10 area, etc), breaking will still occur until the actor is moved suffeciently far away.
…o Create contraptions (sorry for all the buggy builds)
The entity's OBB yaw was computed from subLevelPose before that variable
is assigned for the current substep and sub-level, so the collision box
was oriented to the previous substep's pose - or to a different
sub-level entirely when several are loaded. On rotated sub-levels this
makes entities catch on walls and doorframes that their (correctly
oriented) box should clear.

Compute the orientation inside the per-sub-level loop, right after the
substep pose lerp. The custom-orientation composition matches
transformEntityBoundingBox; the up-direction and bounds-center handling
that also performs depends only on the custom orientation, not the yaw,
so it does not need to be redone here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fill in the stubbed EntitySubLevelUtil.getCustomEntityOrientation:
entities standing on (tracking) a tilted sub-level adopt the tilt
(swing) component of its orientation, keeping their own yaw, between
1 and 45 degrees of tilt. Below the lower bound the sub-level is treated
as flat and vanilla behavior applies; above the upper bound the entity
stays upright rather than being pitched onto its side.

The orientation is decomposed swing-twist about world Y so the twist -
the sub-level's own heading - is discarded: a deck rotating underfoot
must not drag the entity's heading with it.

All existing custom-orientation plumbing (rendering, shadows, camera,
collision OBB, jumping, effects) engages through this stub, so filling
it in is what makes surface orientation live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three fixes that together make standing, walking and sneaking on a
tilted sub-level behave the way flat ground does.

Gravity press (LivingEntityMixin): grounded on a tilted surface,
world-frame gravity leaves a tangential remainder every tick
(~g*sin(tilt)) that vanilla never absorbs, because vanilla floors are
always horizontal - entities creep downhill with no input. Simply not
applying gravity breaks contact: the constant press into the floor is
what keeps ground detection engaged, and without it motion decays to
zero, contact is lost, and the entity enters a fall-catch-slide cycle.
Aim the press along the surface normal instead, so contact stays
engaged and the ground resolution absorbs it exactly. Rising jump ticks
keep plain world gravity, detected by the velocity component along the
SURFACE normal rather than world Y - walking uphill has positive
world-Y and would otherwise be misread as a jump, starving the contact
press for a tick and letting world gravity stutter in.

An earlier iteration of this instead absorbed sub-threshold tangential
motion in Block.updateEntityAfterFallOn. That only ran on landing, so
it could not hold a standing entity in place, and it is superseded by
the press model above.

Position reconstruction (SubLevelEntityCollision): reconstruct the
entity position as the exact inverse of how entityBoundsCenter was
derived from it. The previous reconstruction subtracted half the body
height along entityUp, which under a custom orientation displaces the
entity laterally by ~eyeHeight*sin(tilt) every tick - a phantom
conveyor on any tilted surface.

Pose fitting (PlayerMixin): vanilla's canPlayerFitWithinBlocksAndEntities
check tests the upright, world-aligned box, but a surface-oriented
player's real box is tilted with the surface and clears geometry the
upright box cannot, so the check spuriously forces the crawling pose in
doorways. Skip the pose demotion while surface-oriented; physical
obstruction is still enforced by the oriented collision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Picking (ProjectileUtilMixin): entity picking clips the look ray
against the target's world-aligned AABB, but a surface-oriented entity
renders tilted, so aiming at the visible model can miss the box. AABBs
cannot rotate, so counter-rotate the ray into the entity's frame around
its feet pivot (matching the render pivot), clip against the plain box,
and transform the hit back - geometrically identical to picking an
oriented box. Entities without a custom orientation take the original
call unchanged.

The candidate loop hands a null entity through ProjectileUtil, so the
orientation lookup is null-guarded both here and at the API; without it
this NPEs on the render thread.

Lighting (EntityRendererMixin): probe from the oriented body center
rather than the oriented eye - on tilted decks the tilted eye lever can
lean the probe into adjacent geometry, sampling zero light and
rendering the entity black. The body center is inside the entity's own
volume and so is always in open space.

Also require the probe to land in a sub-level plot's AIR before letting
that sub-level darken the entity's sky light. When a neighboring
contraption's world bounds overlap the entity, the inverse transform
drops the probe inside that sub-level's solid geometry - a position the
entity cannot really occupy - and its sky=0 would otherwise min() the
entity to pitch black. Landing in air is the genuine interior-shadow
case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The per-tick [collide dbg] logging in SubLevelEntityCollision.collide is
not shippable (fires every tick, client-side, per surface-oriented
player), but it is the fastest way to inspect the surface-orientation
force model when that work resumes.

Record the snippet, its insertion point, the meaning of each field and
the locals it depends on, so it can be dropped back in without digging
through the feature branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit afec269)
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f0e88183-e4ba-431e-b7fa-7e41c5cad6eb)

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.

7 participants