← Back to homepage

Build a game with an LLM

nape-js ships machine-readable docs, so Claude, ChatGPT, Copilot and Cursor can write against it without guessing. What they still get wrong is the handful of places where this engine differs from Box2D and Matter.js — the prompts below front-load exactly those, so you spend your time on the game instead of on debugging invented API calls.

1. Give the model the docs

Start every session with this. It points the assistant at the full API reference in a single file — roughly 76 KB, well within a modern context window — so it stops inferring method names from other engines.

Context primer — paste first
I'm building a 2D game with @newkrok/nape-js, a TypeScript physics engine.

Before writing any code, read the full API reference:
https://raw.githubusercontent.com/NewKrok/nape-js/master/packages/nape-js/llms-full.txt

Key rules for this engine, which differ from Box2D/Matter.js:
- Bodies join a world by assignment: `body.space = space` (there is no `space.add()`).
- Shapes attach the same way: `body.shapes.add(new Circle(20))`.
- There is NO `body.applyForce()`. Assign `body.force = new Vec2(x, y)` instead,
  and clear it each frame — it persists across `space.step()`.
- `body.applyImpulse(vec)` DOES exist and is the usual way to push things.
- Gravity is a Vec2 on the Space: `new Space(new Vec2(0, 600))`. Y points down.
- Step with a fixed timestep: `space.step(1 / 60)`.

Use TypeScript. Ask me before inventing any API you did not see in that file.
Working in Cursor, Claude Code, or another repo-aware tool? Save that text as .cursorrules, CLAUDE.md, or .github/copilot-instructions.md in your project and it applies to every request automatically.

2. Pick a starting point

Each of these assumes the primer above is already in context. They are deliberately specific about the physics setup, because that is the part models improvise badly.

Platformer

Side-scrolling platformer
Build a side-scrolling platformer with nape-js and a canvas renderer.

Requirements:
- Use the built-in `CharacterController` helper for the player rather than
  hand-rolling ground checks — it already handles slopes, coyote time,
  one-way platforms, and moving-platform inheritance.
- Level geometry: static bodies with `Polygon.box()` shapes.
- A camera that follows the player, updated ONLY on physics steps (not on
  every animation frame) so movement doesn't stutter on 120Hz displays.
- Fixed timestep with an accumulator; render interpolation between steps.

Note: `CharacterController` ORs a bit into the body's collision filter group,
so if I later add projectiles, account for that when setting up filtering.

Top-down / arena

Top-down arena with AI opponents
Build a top-down arena game with nape-js. No gravity: `new Space(new Vec2(0, 0))`.

Requirements:
- Player and AI agents are `Circle` shapes (circles steer and slide cleanly;
  boxes snag on corners).
- Set `body.allowRotation = false` on agents so they don't spin on contact.
- Movement by setting `body.velocity` directly, not by applying force.
- Walls as static bodies around the play area.
- Simple AI: steer toward a target, with a dead zone around the facing angle
  so agents don't jitter between left and right when nearly aligned.
- Detect "is A touching B" by polling `body.interactingBodies()` each step
  and calling `.clear()` on the returned list — simpler than an
  InteractionListener when you only need a boolean.

Physics puzzle

Puzzle / sandbox with joints
Build a physics puzzle game with nape-js where the player drags objects
to solve each level.

Requirements:
- Mouse dragging via a `PivotJoint` between a static "hand" body and the
  grabbed body — create it on mousedown, remove it on mouseup.
- At least one level using a `DistanceJoint` rope and one using a
  `PulleyJoint`.
- A win condition checked with an `InteractionListener` on a sensor shape.
- Soft joints where a rope or spring rests against the floor: set
  `stiff = false` with a frequency around 12 and damping 1. A fully rigid
  joint fights the floor contact and jitters.

Deterministic multiplayer

Server-authoritative multiplayer
Build a server-authoritative multiplayer prototype with nape-js.

Requirements:
- Server runs the simulation with `space.deterministic = true` and a fixed
  timestep; clients render interpolated snapshots.
- Use `@newkrok/nape-js/serialization` (`spaceToJSON` / `spaceFromJSON`) for
  the initial world sync, then send per-frame deltas for bodies whose
  position or rotation actually changed.
- Set `body.isBullet = true` on fast-moving bodies to enable CCD.

Read the multiplayer guide before designing the protocol:
https://github.com/NewKrok/nape-js/blob/master/docs/guides/multiplayer-guide.md

Determinism here is same-platform only — floating-point results differ across
CPU architectures, so don't design for cross-platform lockstep.

3. Mistakes models reliably make

These come from real sessions. Paste the relevant one when you hit it — or add them to your project rules up front.

A Polygon falls straight through the floor
A dynamic Polygon constructed with an explicit Material can tunnel through static polygon floors — isBullet does not help. Fix: omit the material argument and let the engine use its default (new Polygon(Polygon.box(w, h))), or use a Circle or Capsule, which are unaffected. Most likely to bite in car and vehicle demos, where both the chassis and the track walls are polygons.
Invented method: body.applyForce()
It doesn't exist. Assign body.force = new Vec2(x, y). The value persists across steps, so zero it each frame unless you want it to accumulate. body.applyImpulse(vec) is a real method and is usually what you actually want.
Rotating a shape doesn't rotate its collision geometry
Setting shape.rotation changes nothing about how the shape collides. Rotate the body instead: body.rotation = Math.PI / 2.
Reading space.arbiters throws "Index out of bounds"
space.arbiters.at(0) throws when the list is empty, and .length is undefined. Get the count with space.arbiters.zpp_gl() and guard before indexing. Note a sleeping body reports zero arbiters, so use body.isSleeping as a fallback in ground checks.
Material with density 0 throws a mass error
The signature is new Material(elasticity, dynamicFriction, staticFriction, density). A density of 0 yields zero mass on a dynamic body and errors out. Models often pass 0 meaning "default".
An AngleJoint does nothing inside its range
With jointMin < jointMax the joint is a pure range limit: no force at all while the angle sits inside the window. For a spring that always pulls toward a target angle, set jointMin === jointMax.
The camera stutters at 120Hz but the physics looks fine
Camera interpolation running on every animation frame while bodies move only on fixed physics steps reads as stutter. Gate camera updates behind a "stepped this frame" flag so both advance together.

4. What to hand the model next

Beyond the primer, these are the files worth pasting or linking when a task goes deeper.

One habit worth keeping: ask the model to run the game and describe what it sees, or to write a short headless harness that steps the space and asserts on positions. Physics bugs are visual and behavioural, and code that reads correctly can still simulate wrongly.