# Platformer race template (SKYLEDGE)

> SKYLEDGE is a side-view platformer race for up to eight players with coyote time, jump buffering, variable jump height, an air dash, springs, moving platforms, per-player coins and seat bots that run planned routes. Start it with one command and change it in src/game.ts.

Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under `@netforge` with source at https://github.com/netnetdev/net-forge.

Source: https://forge.netnet.capital/docs/templates/platformer  
Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0.

SKYLEDGE is a complete multiplayer platformer race: up to eight climbers race along cliffside courses to the flag, with tuned jump feel (coyote time, jump buffering, variable jump height, an air dash), springs, one-way ledges, moving platforms and thorns. A round is one course; seat bots fill empty places so one person still has a race.

```sh
npm create netforge@latest my-game -- --template platformer --yes
cd my-game
npm run play
```

Keys: A D or arrows to run, Space W or Up to jump (hold for height), Shift to dash, Down plus Jump to drop through a ledge, R or Enter to ready the next course.

## What you get

| Part | What it does | Where |
|---|---|---|
| Courses | Blocks, one-way ledges, thorns, springs, moving platforms, coins and checkpoints, in metres | `levels` in `game({...})` |
| Motor | Run speed, jump height, time to apex, fall gravity, jump cut, coyote and buffer windows, air jumps, dash | `motor` in `game({...})` |
| Race rounds | Countdown, race, results, next course; places in order of finishing | the engine's race runner |
| Route planner | Checks every course can be finished, and drives the seat bots | runs in the build and in `npm test` |
| Netcode | 60 Hz deterministic platform physics on a 1/1024 m grid; your climber is predicted bit for bit | the template package |
| Playtest bots | Four climbers plus seat bots for four minutes: rounds, finishes, falls, earn | `npm test` |

Measured on an Apple M2 Pro, Node 22: four playtest climbers plus seat bots over four minutes (14,400 ticks at 60 Hz) ran 7 courses, all four finished every one, nobody fell, and the server tick stayed under 0.02 ms at p99.

## 1. Build a course

Coordinates are metres in side view: x runs right from the start to the flag and y runs up. `blocks` are `[x, y, width, height]` from the bottom-left corner. A full running jump clears about 6 m and about 8.5 m with the dash.

```ts title=src/game.ts
import { game } from "@netforge/engine";

const ground = (x0: number, x1: number, y = 0): [number, number, number, number] => [x0, y - 4, x1 - x0, 4];

export default game({
  template: "platformer",
  style: "lowpoly",
  tickRate: 60,
  places: { cliffs: { chunks: "places/cliffs", spawn: [0, 0, 0] } },
  players: { prefab: "style.lowpoly.rogue", max: 8, bots: 3 },
  round: { seconds: 180 },
  levels: [
    {
      name: "First Steps",
      start: [0, 0],
      flag: [70, 2.4],
      blocks: [ground(-8, 24), [14, 0, 3, 1.2], ground(28, 50), [50, -4, 24, 6.4]],
      ledges: [[38, 2.6, 4]],
      hazards: [[33, 0, 2, 0.5]],
      springs: [[45, 0, 21]],
      coins: [[4, 1], [5.2, 1], [6.4, 1]],
      checkpoints: [[30, 0]],
    },
  ],
});
```

Steps up of 1 to 2.5 m and gaps of 2 to 5 m play well, with a checkpoint every 30 to 50 m. If the route planner finds no way to the flag, the build fails and names the course.

## 2. Tune the jump feel

Add `motor: { runSpeed: 8, jumpHeight: 3.6, airJumps: 1 }` to `game({...})`. The ranges are `runSpeed` 3 to 14, `jumpHeight` 1 to 8, `timeToApex` 0.2 to 0.7, `fallGravity` 1 to 3, `jumpCut` 0.2 to 1, `coyoteSeconds` and `bufferSeconds` 0 to 0.25, `airJumps` 0 to 2 and `dashSpeed` 6 to 30. Change a few at a time; the planner re-checks every course with the new numbers.

## 3. Reward a clean climb

```ts title=src/game.ts
import { earn, system } from "@netforge/engine";

system("clean_climb", ({ on, emit }) => {
  const falls = new Map<number, number>();
  on("player.fell", (ids) => {
    for (const id of ids) falls.set(id, (falls.get(id) ?? 0) + 1);
  });
  on("player.finished", (ids) => {
    const clean = ids.filter((id) => !falls.get(id));
    if (clean.length) emit("climb.clean", clean);
  });
  on("run.started", (ids) => {
    for (const id of ids) falls.delete(id);
  });
});

earn.rule("clean_climb", { when: "climb.clean", n: 1, pay: 12, oncePer: "run" });
```

`race.won` and `race.podium` count only when another person was in the round, and seat bots never earn.

## 4. Check it with bots

```sh
npm test
```

## How it works

The simulation is `@netforge/engine-sim`'s platform mode: plain TypeScript swept box movement on a 1/1024 m grid at 60 Hz, so the browser replays its own climber exactly the way the server does. Climbers pass through each other and each collects their own coins, so nothing another player does can make your prediction wrong. That is why a race feels instant even on a slow connection. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md).

The template draws the side view with three.js in every art style. The full guide is [Make a 3D platformer for the web](https://forge.netnet.capital/docs/guides/3d-platformer-web.md).

## Alternatives

- **[Phaser](https://docs.phaser.io/)** has arcade physics and tilemaps and is a common choice for 2D platformers in the browser. Pick it for a single-player platformer with pixel art and hand-made levels.
- **[Godot](https://docs.godotengine.org/en/stable/)** has a 2D physics engine, a character body node and a level editor. Pick it when you want to build levels visually and export to desktop too.
- **Net Forge** fits when you want an eight-player race with server-checked finishes, prediction that never mispredicts, and courses an agent can write as data.

## FAQ

### Does the platformer have coyote time and jump buffering?

Yes: 0.1 s of coyote time and a 0.13 s jump buffer by default, plus variable jump height, a lighter apex while Jump is held, a heavier fall and ceiling corner nudges.

### Can players collide with each other?

No. Climbers are ghosts to each other in this template, which is what lets each browser predict its own climber exactly.

### How do the bots know the course?

A route planner finds a path to the flag on every course, and the seat bots run those routes. The same planner is the build's softlock check.

### How many players can race?

Up to eight per room, with seat bots filling places up to `players.bots`.

### Is it 2D or 3D?

The game plays in side view, and the template renders it as a 3D three.js scene in any of the six art styles.
