Co-op movement

For co-op, players are on the same side, so cheating barely matters. Keep movement client-owned for zero-latency feel, and add one small validator so a bug (or a bad actor) can’t teleport across the map.

Schema

export const schema = defineSchema({
  players: map(entity({ x: number(), y: number(), vx: number(), vy: number() })),
});

Room file

Give each joiner ownership, and clamp movement speed server-side:

export default defineRoom(schema, {
  onJoin(state, ctx) {
    state.players[ctx.clientId] = { x: 0, y: 0, vx: 0, vy: 0, owner: ctx.clientId };
  },
  validate: {
    players(prev, next) {
      const max = 16;
      next.x = clamp(next.x, prev.x - max, prev.x + max);
      next.y = clamp(next.y, prev.y - max, prev.y + max);
      return next;
    },
  },
});

Client

function update(dt: number) {
  const me = room.state.players[room.me];
  me.x += input.x * SPEED * dt;
  me.y += input.y * SPEED * dt;
}

Smoothing remote players

Remote entities arrive at the room’s sync rate, not your frame rate. Interpolate toward their latest value for buttery motion:

ghost.x += (target.x - ghost.x) * 0.25;
ghost.y += (target.y - ghost.y) * 0.25;

Next steps

Placeholder guide.