Motion engine
How walking, sitting down and standing up are computed. You do not need this to build an app, but it helps when you make assets, plug in another renderer, or fix odd-looking motion.
Flow
plan (extended every tick, deterministic) draw (every frame)
createBehaviorWorld ── frameAt(t) ─┬─ overrides position·facing·pose ─┐
routines·commands·paths·booking ├─ transitions sit/stand progress ├─ behaviorActors ─ actorPartBoxes ─ composeRender ─ SVG
└─ strides distance walked ─┘ (part boxes) (depth sorting)- Planning decides who goes where, when, and what they do there. It chains segments (
idle,walk,wait,stand-up,sit-down,act,away). It depends only on time, so you can ask about any t. - Drawing computes that moment's part boxes in world space and depth-sorts them. Nothing is slid around in screen coordinates, so
- walking people do not jitter (speed is not uneven from bridging between ticks), and
- nobody appears to pass through furniture (each part is ordered front-to-back from its real position).
Walking
Paths are horizontal/vertical A* on a 0.25-tile grid (buildNavGrid, findPath). Walking people reserve nodes in time slots, so they detour or wait for each other.
Legs and arms swing with the distance walked. Feet do not slide even at higher playback speeds.
Value (GAIT) | ||
|---|---|---|
cycle | 0.9 tiles | Distance for one left step and one right step |
swing | 0.06 | How far legs swing forward and back |
arm | 0.05 | Arm swing (opposite to the legs) |
bob | 0.02 | How much the body rises when the feet pass each other |
ease | 0.15 tiles | Distance over which the swing grows after starting and shrinks before stopping |
- Swinging parts are found by name:
leg|shoe|shorts-l/rare legs,arm|forearm|hand-l/rare arms (gaitLimb). - Speed:
WALK_SPEEDis 1.6 tiles/s. Change it withcreateBehaviorWorld(…, { speed })oruseBehaviorPlayback(…, { walkSpeed }). - Facing changes in 90° steps, turning instantly at corners.
Sitting down and standing up
Sitting takes POSE_SECONDS (0.45 s), in two stages.
① Fold (0–45%) ② Move (45–100%)
Fold the body into the seated shape Move that shape, unchanged, onto the seat
where they stand
- First turn to face the seat - The shape never changes, so if the seated
- Height first (lift knees, raise hands), pose clears the furniture and the path is
reaching forward (legs, hands) later clear, nothing passes through furniture
- Thighs and shins come from the upper - Onto a higher seat: match the height
and lower halves of the legs; feet are first, then move
shaped from the shoes - Onto a lower seat: move, then lower- Standing up runs the same path in reverse: slide out of the seat, then unfold.
- Lying down (beds) lays the body down at the height of the lying surface. Parts swing back in an arc (90°) around the feet, then that shape moves onto the bed unchanged. The standing place is searched beside the bed (at the hip), stepping outward 0.25 at a time, or at the foot if blocked. On a bed for two, each person climbs in from their own side.
- Parts that exist only when standing (a bag in hand, a skirt hem) shrink away while folding; parts that exist only when seated (forearms reaching forward) grow from a point.
- When entering from the front, the fold is pulled toward the seat so the folded toes do not go past the front edge of the standing place (so feet do not hit a coffee table in front of a sofa).
actorPartBoxes(object, asset, { transition: { from, progress } })gives this shape.fromis the pose before the transition (position, facing, pose).
Which side to enter from
For each seat, four standing places are considered (standUpCorners): front → left → right → back.
- It must be standable (
canStand: no overlap with furniture or walls, and on floor tiles). - Among those, pick the first where the whole sitting motion passes through no furniture (
sitPathClear). It is measured with every person asset in the scene. - If there is none, the first standable place.
That is why a chair pushed up to a desk is entered from the side (desk in front, chair back behind), and a sofa from the front. The editor's "stand up" picks the standing place by the same rule.
Known limitation: when no side is clear — a chair tight against table legs, or a wheelchair with wheels at the sides — feet briefly brush the legs or wheels as the person sits.
Collision check API
| Function | Meaning |
|---|---|
transitionClear(seated, asset, from, obstacles) | Whether one person sitting from from to seated stays clear of obstacles (boxes). The fold is measured in slices, the move over its whole swept area. Contact already present in the start or end pose is not counted |
sitPathClear(seated, standing, sitters, obstacles) | Measures with several person assets, using only nearby obstacles |
seatObstacles(scene, registry, ignore?) | Obstacle boxes in the scene (without floor tiles and rugs, and without the people in ignore) |
flatOnFloor(asset) | Whether the asset is a thin floor covering drawn before everything else, like floor tiles and rugs (not an obstacle) |
Drawing
prepareRenderdraws static things once and measures front/back edges between parts. It sorts by screen x and only compares pairs whose x ranges overlap, so a 600-part scene takes about 1.7 ms.composeRenderslots only the moving people's parts in between those edges every frame and sorts topologically.- It re-measures only the static parts that overlap a person's silhouette, so 8 people take around 1 ms.
- Front/back is decided between part boxes: two boxes that overlap on screen are separated along some axis, and that axis decides which is in front (
isBehind). Floor tiles and coverings are drawn first, separately. SceneLayerkeeps React children in a fixed order and moves as few DOM nodes as possible to apply the draw order (longest increasing subsequence). In browsers withmoveBefore, moved nodes keep their screen and typing CSS animations running instead of restarting.- The clock's
timeAt(wall)returns the same value anywhere within one frame, so several sources drawn in one frame never fall out of step.
Numbers
| Walking speed | 1.6 tiles/s (WALK_SPEED) |
| Sitting down / standing up | 0.45 s (POSE_SECONDS) |
| Pathfinding grid | 0.25 tiles (NAV_STEP), person footprint 0.5×0.5 (PERSON_SIZE) |
| Tick | 80 ms (TIMELINE_TICK_MS) — how often plans, activities and bubbles change |
| Sitting template | footprint 0.5×0.75, back y 0.1, knees y 0.46, hips z 0.49 (SITTING) |
| Lying template | footprint 0.5×1.4, head end y 0, back z 0 (LYING) |
| Building a world | startup office (8 people, 20 spots) about 6 ms — including the entry-side collision checks |