Avatar game feel: gaits matched to the ground, jump, camera collision, city interaction and audio #30
Labels
No labels
agent
agent:ci
agent:done
agent:failed
agent:needs-input
agent:refined
agent:refining
agent:running
agent:skip
autonomous
driven
local
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jeroen/cartopolis#30
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Walking around the city read as "sluggish and unrealistic". The input path was
not the cause, and neither was the network — this ticket covers the five things
that actually were, plus the world-contact work that fell out of fixing them.
The code is written. Eight commits sit on this branch; the ticket is the
record and the review handle, not a request to start.
Problem
The feet moved at half the speed of the ground, permanently. Input reaches
the display in one frame — a key press sets
predicted_velocityandsmooth_online_avatar_displayintegrates it the same frame. What read as lagwas foot-slide, and it was arithmetic:
WALK_SPEEDwas 8.0 m/s and was the only speed — a world-class sprintused for a stroll along a canal.
Runclip is 0.8 s/cycle ≈ 4 m/s of real stride, played at ahard-coded
set_speed(1.0).Walk(clip 22) was never loaded; the client knew only Idle (4) and Run (16).Gravity was a constant 12 m/s descent with no acceleration in it, so
stepping off a kerb and falling off a tower began identically.
The motion constants existed in three private copies (sim, client
prediction,
--offlinepath), each carrying a comment asking the next person tokeep them in step. A mismatch between the first two is invisible except as
rubber-banding; between the first and third the same key produces a different
world depending on how you launched.
The camera went through walls, had no reaction to a landing, and gave no
sense of pace.
Nothing streamed reacted to the avatar being near it. Picking was prim-only
and edit-oriented; benches and shops were scenery you walked through. The
poisand
addresseslayers were decoded on every streamed cell and thrown away — 467and 6,211 features in the Groningen fixture
(
docs/notes/vector-tile-unused-layers.md).There was no audio at all.
bevy_audiowas dropped when the feature listwas slimmed. A city you can walk through in silence reads as a viewer, not a
place.
Approach
A shared speed model.
crates/core/src/motion.rsholds what the sim, theprediction and the offline path must agree on —
sim_speed_for_wish(:40),GRAVITY = 22.0(:69, above Earth's so a jump does not hang like the moon)and the integrated
step_fall(:94).systems::locomotionholds the client'shalf: stroll 1.4 / jog 4.2 / sprint 8.0 m/s
(
crates/viberfox/src/systems/player/locomotion.rs:31,34,38) and the clipreference speeds. Gaits cost nothing on the wire —
apply_intentderivesspeed from the wish vector's magnitude, so a gait is a magnitude the client
picks. Speed ramps in m/s² rather than easing a normalized throttle, or a sprint
start feels exactly as light as a walk start.
Clips matched to the ground.
Walkis loaded, the gait is chosen by groundspeed with hysteresis, and playback rate is
ground_speed / clip_reference_speedso the stride covers the distance travelled. Changes cross-fade through
AnimationTransitionsinstead ofstop/play.Protocol 13 (
crates/core/src/protocol.rs:56) for the two things that didnot fit through the magnitude throttle.
ClientIntent::jumpis a request thesim grants only from the ground, so holding the bit cannot climb — a predicted
jump is reconciled straight into the floor, since
SimWorld::stepclampsy >= 0. Emotes replicate as a level held on the wire for a couple of secondsso a client arriving mid-wave sees it, which is why they carry a sequence number.
The follow boom collides in 2D and only below 15 m
(
free_camera.rs:230) — footprints carry no height, so above an eaves line a 2Dhit means "there is a building over there", not "the view is blocked". In fast,
out slow, or every doorway makes the camera lunge. A landing dips the eye scaled
by impact speed; FOV widens toward a sprint. Driver mode is exempt.
The city answers back.
crates/geo/src/places.rsextracts the two droppedlayers out of the
TileDatamap_geometryalready parses — no extra request,no extra decode.
systems::world_placeskeys them by cell in the anchored worldframe and drops them with the cell;
systems::interactis the proximity promptand the E key. Street furniture becomes circle colliders.
Audio synthesised at startup (
systems::soundscape) rather than shipped:.gitattributestracks audio by extension, so a shipped footstep would be an LFSobject with the documented silent-failure mode. Footsteps are counted off the
same ground speed that sets the clip rate, so they stay in phase by construction.
Acceptance criteria
Shiftsprints,AltstrollsSpacejumps from the ground and takes off if held past 0.25 s; the simgrants it, so prediction is not reconciled into the floor
--offline1–6play emotes, replicated so a client arriving mid-emote sees itEacts on itValue::Stringsilently dropped most)--no-default-features --features solostill builds on a host with no ALSAVerification
Run in this container on 2026-08-10, all green:
New system prerequisite: the
audiofeature (on by default) pullsbevy_audio → rodio → cpal → alsa-sys, whose build script panics withoutlibasound2-dev— it is a pkg-config shim with no vendored fallback. Any Linuxbuilder needs the package, or
--no-default-features --features solo. Installedin the agent container and added to its image definition on 2026-08-10.
Not verifiable here, needs a workstation: this container has no GPU and no
Vulkan ICD, so nothing was seen. The camera boom, the landing dip, the FOV
coupling, the flight/jump pose and the interaction prompt's placement are all
visually unchecked.
cargo shotsand a real walk around a city are themissing gate.
Out of scope
Run_Left/Run_Right/Run_Back;the avatar turns to face its wish direction instead, deliberately.
cannot bind (different rig) — flight and jump are poses on the model
holders, not clips.
editing 20 glTFs; steps are counted from ground speed instead.
client-side prediction against streamed footprints.
camera test at 15 m and skips avatar collision while airborne.
Branch:
feat/30-avatar-game-feelReady for review — branch
feat/30-avatar-game-feel@28a9915.PR: https://code.garage44.eu/jeroen/viberfox/compare/main...feat/30-avatar-game-feel
mainmoved three times while this was in flight (#28, #33, #35), so the branchcarries two merges of
main. The one that mattered is #33, the POI-markers work:it and this branch both read the tile's
poislayer. Resolved by keeping bothmodules —
placesanswers "what can the avatar walk up to",poisanswers "whatstands on the map" — with the reasoning in the merge commit and the note updated
to record three readers of one layer. Unifying them is a follow-up, not part of
this.
Also fixed here: the eight feature commits had never been through CI's fmt gate and
failed it at 14 sites (
ef83f60).origin/mainpasses it clean, so they allarrived with this work.
Verified on the merged tree
cargo fmt -p viberfox -p viberfox_geo -p viberfox_core -p viberfox_simulator -p viberfox_android --checkcargo check --workspace --all-targetscargo test -p viberfoxcargo test -p viberfox_geocargo test -p viberfox_corecargo test -p big_space --libNot verified, and cannot be here
so the camera boom, the landing dip, the FOV coupling, the flight/jump pose and
the interaction prompt's placement are unseen.
cargo shotson a workstation isthe missing gate.
scene-smoke(no GPU).New build prerequisite
The
audiofeature (default on) pullsbevy_audio -> rodio -> cpal -> alsa-sys,whose build script panics without
libasound2-dev— a pkg-config shim with novendored fallback. Any Linux builder needs the package, or
--no-default-features --features solo.