---
title: "Moving platforms, elevators, rotating hazards, and objects on a patrol path"
description: "A platform slides side to side, a lift rises and falls, or a blade spins. There is no `MotionComponent` (stripped); self-driven motion is a tick handler that patches the `TransformComponent` every frame."
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/knowledge/playbooks/moving-platforms-and-elevators/
---

# Moving platforms, elevators, rotating hazards, and objects on a patrol path

## When to use

A platform slides side to side, a lift rises and falls, or a blade spins. There
is no `MotionComponent` (stripped); self-driven motion is a tick handler that
patches the `TransformComponent` every frame.

## The recipe

Author with `project_apply_script_semantic_patch` using the `ai_safe` op pack.
The mover is an entity with a `TransformComponent` and usually a solid
`ColliderComponent` so the player can stand on or be blocked by it.

1. Add a tick handler: `addTickHandler` `{ "handlerKey":"onMove" }`.
2. In the body (whole-body GessaScript via `replaceScriptFromGessaScript`),
   read the current transform with `ctx.entity.readComponent`
   (`TransformComponent`), compute the next position, and write it with
   `ctx.entity.patchComponent`. Scale every delta by `ctx.delta` (seconds since
   last tick) so speed does not depend on frame rate.
3. Oscillate back and forth: keep a phase in durable state, advance it each tick
   with `incrementNumericState` `{ "field":"phase", "by": <speed * delta> }`,
   and map it to an offset (a triangle or sine of the phase). Or ping-pong:
   track a `goingUp` flag with `setBooleanState`, move toward the current
   endpoint, and flip the flag when you pass it.
4. Rotate a blade or gear: patch `rotation` instead of `position`, adding a
   per-tick angle. Parent the visible mesh under a pivot entity with
   `ctx.entity.setParent` when the spin axis is not the mesh center.
5. Timed lift (moves only on an interval): drive it from a `TimerComponent` plus
   `addTimerHandler` instead of every tick, patching to the next stop each fire.

## Pitfalls

- Multiply every movement by `ctx.delta`. A fixed per-tick step drifts when the
  tick rate changes and desyncs between clients.
- Patch `position`/`rotation` through `ctx.entity.patchComponent`; do NOT write
  `VelocityComponent` (it is engine-internal and hidden from AI/SDK authoring).
- Transform patches are server-authoritative (`hierarchy.server_authority`), so
  the platform moves identically for all players; ride-along works because the
  player stands on a solid collider.
- Store oscillation phase or the ping-pong flag in durable state, not a local
  variable, so the platform keeps its cycle across a reload.
- Carry the rider by making the platform solid and letting the physics step
  resolve standing contact; do not teleport the player each tick.

## Verify

- `simulation_run` (`qa.run.start`) to watch the platform complete a full cycle
  and confirm a standing entity is carried and a solid mover blocks passage.
- `project_get_graph_snapshot` to confirm the tick or timer handler landed.
