Smooth the pawn's vertical step between terraces #92

Open
opened 2026-09-13 14:21:29 +01:00 by liamjd · 0 comments
Owner

Follow-up from #80, which is merged and correct as specified — this is presentation polish on top of it, not a bug fix.

PawnView._move_to_ground() resamples Y from map.height_at() every frame and assigns it directly. That is the right ground truth: the pawn stands on the terrace it is actually on, never inside a riser and never floating over one. But the transition is instantaneous — the pawn's Y jumps a full level_height in the single frame its XZ crosses the tile boundary.

Evidence

tools/probe_pawn_terrace.gd walks a pawn over a staircase (up three terraces, down two) and drops a marker every 0.2s. At 1.5 tiles/s the pawn covers 0.6 world units between samples, and a riser is 0.5 units tall — yet there is no intermediate sample on any riser. Every sampled Y is exactly surface_y(level) + pawn_height / 2:

[probe]   32   7.80   2.30   1.50
[probe]   36   8.40   2.80   2.00     <- riser, no value between 2.30 and 2.80

The trail image is a clean staircase of dots. That is the correct shape; the issue is that in motion it reads as a teleport rather than a step up.

Secondary, and probably not worth solving separately: at the boundary frame the capsule's lower half briefly intersects the riser corner, because the pawn is a 0.25-radius capsule being positioned by a point sample.

Why this is not just "interpolate Y"

The obvious fix — ease Y toward the sampled surface over a few frames — would break test_height_steps_with_the_terrain_instead_of_ramping_between_waypoints in test/unit/test_pawn_view.gd, which asserts the snap deliberately. That test is right about the model: the pawn is on one terrace or the other, never halfway up a cliff face. So the smoothing belongs in a presentation layer that lags the authoritative Y, not in the model.

That distinction matters for anything that later reads a pawn's position — a camera follow, a held-item attachment point, a selection ring — which should track the smoothed visual, while pathfinding and cell keep using the snapped truth.

Open questions

  • Ease Y only, or also a small hop/lean so a step up reads as effort rather than as a lift?
  • Does a descent get the same treatment, or should falling be faster than climbing?
  • Should the smoothing time be fixed, or scale with speed so a fast pawn does not trail its own feet?
  • Is this better as a PawnView detail, or as a general "visual Y lags logical Y" helper that vehicles and haulers reuse later?

AC

  • A pawn crossing a riser shows intermediate Y values between the two terrace heights.
  • The pawn is never visibly inside the terrain or floating above it for more than the smoothing window.
  • cell, arrived and pathfinding still work off the snapped position, so nothing downstream sees the lag.
  • Re-running tools/probe_pawn_terrace.gd produces a trail with dots on the risers rather than a bare vertical gap.
Follow-up from #80, which is merged and correct as specified — this is presentation polish on top of it, not a bug fix. `PawnView._move_to_ground()` resamples Y from `map.height_at()` every frame and assigns it directly. That is the right ground truth: the pawn stands on the terrace it is actually on, never inside a riser and never floating over one. But the transition is instantaneous — the pawn's Y jumps a full `level_height` in the single frame its XZ crosses the tile boundary. ## Evidence `tools/probe_pawn_terrace.gd` walks a pawn over a staircase (up three terraces, down two) and drops a marker every 0.2s. At 1.5 tiles/s the pawn covers 0.6 world units between samples, and a riser is 0.5 units tall — yet there is **no** intermediate sample on any riser. Every sampled Y is exactly `surface_y(level) + pawn_height / 2`: ``` [probe] 32 7.80 2.30 1.50 [probe] 36 8.40 2.80 2.00 <- riser, no value between 2.30 and 2.80 ``` The trail image is a clean staircase of dots. That is the correct shape; the issue is that in motion it reads as a teleport rather than a step up. Secondary, and probably not worth solving separately: at the boundary frame the capsule's lower half briefly intersects the riser corner, because the pawn is a 0.25-radius capsule being positioned by a point sample. ## Why this is not just "interpolate Y" The obvious fix — ease Y toward the sampled surface over a few frames — would break `test_height_steps_with_the_terrain_instead_of_ramping_between_waypoints` in `test/unit/test_pawn_view.gd`, which asserts the snap deliberately. That test is right about the *model*: the pawn is on one terrace or the other, never halfway up a cliff face. So the smoothing belongs in a presentation layer that lags the authoritative Y, not in the model. That distinction matters for anything that later reads a pawn's position — a camera follow, a held-item attachment point, a selection ring — which should track the smoothed visual, while pathfinding and `cell` keep using the snapped truth. ## Open questions - Ease Y only, or also a small hop/lean so a step up reads as effort rather than as a lift? - Does a descent get the same treatment, or should falling be faster than climbing? - Should the smoothing time be fixed, or scale with `speed` so a fast pawn does not trail its own feet? - Is this better as a `PawnView` detail, or as a general "visual Y lags logical Y" helper that vehicles and haulers reuse later? ## AC - A pawn crossing a riser shows intermediate Y values between the two terrace heights. - The pawn is never visibly inside the terrain or floating above it for more than the smoothing window. - `cell`, `arrived` and pathfinding still work off the snapped position, so nothing downstream sees the lag. - Re-running `tools/probe_pawn_terrace.gd` produces a trail with dots on the risers rather than a bare vertical gap.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
liamjd/UntitledColonyBuilder#92
No description provided.