# Context — platformer-2d (Incanto)

## Project Overview

**Castle Run** — a polished 2D adventure PLATFORMER, the clone-and-modify
starter for the platformer genre. You run a medieval knight through a castle:
solid ground with gaps, floating platforms, a horizontal patrolling platform and
a vertical bobbing lift you RIDE, spinning coins + gems to grab, spikes and a
bottomless pit to avoid, patrolling goblins you STOMP from above (and chain-
bounce off), a checkpoint flag, and a gold goal flag. Three hearts per life,
three lives; touch the goal to clear the castle, run out of lives for GAME OVER.

The level is BUILT-IN gameplay wired in scene JSON. What's custom is the
platformer GAME FEEL the renderer-agnostic library leaves open: a hand-tuned
`PlayerController` (coyote-time, jump-buffer, double-jump, variable jump height,
stomp, knockback, hearts+lives, checkpoint respawn, moving-platform carry) plus
presentation glue (`GoblinSkin`, `FollowCam` with screen-shake, `ParallaxLayer`,
`HudUpdater`).

## Tech Stack

_Exact versions are in `package.json`._

- **Game engine**: `incanto` (scene-JSON-first, three.js-rendered) +
  `incanto/2d` (createGame2D, 2D nodes: CharacterBody2D/StaticBody2D/Area2D,
  AnimatedSprite2D, ColorRect2D, Camera2D, UILayer/Label, Particles2D,
  AudioPlayer) + `incanto/gameplay` (auto-registered Pickup, Patrol, Oscillate,
  ScoreKeeper, …).
- **Art**: built-in animated sheets `medieval-knight` (player) + `goblin`
  (enemy), and `coin` + `gem` item textures — all bundler-imported from the
  package and injected into the scene asset urls in `App.tsx`. Ground, platforms,
  spikes, flags and parallax castle are styled `ColorRect2D`.
- **Audio**: zero-asset procedural SFX presets (jump/coin/hit/hurt/powerup/
  win/lose); an OPTIONAL `engine.music` hook for a looping track.
- **Build / Lang**: Vite, TypeScript. **Headless verify**: `incanto/test`
  (`runScript`) — see `verify.ts`. No React — a single full-window canvas.

## Critical Memory

- READ THE SKILLS FIRST: `node_modules/incanto/skills/` —
  `incanto-gameplay-behaviors.md`, `incanto-physics-and-input.md` (2D bodies +
  input + units), `incanto-building-2d-games.md`, `incanto-audio.md`.
- MOVEMENT is the custom `PlayerController` (NOT the built-in
  `CharacterController2D`, which is frame-perfect/stiff). It integrates velocity
  on the `CharacterBody2D` and adds the forgiveness a good platformer needs:
  coyote-time, jump-buffer, double-jump, variable jump height (release early =
  short hop), stomp-to-kill + bounce, side-hit knockback + i-frames.
- DAMAGE IS CENTRALISED + GROUP-DRIVEN: the player has NO built-in `Health`.
  `PlayerController` owns hearts (3) + reads `ScoreKeeper.lives`, and resolves
  ALL contact each frame by AABB against GROUPS — `enemy` (stomp from above /
  hurt on the side), `hazard` (spikes), `pit` (death plane), `checkpoint`,
  `goal`, `platform` (rideable). One authority → no stomp-vs-damage double-hit.
- RESPAWN is IN-LEVEL: `Health` can't revive (dies once, `heal` is a no-op when
  dead), so the player uses hearts/lives instead and `PlayerController` teleports
  to the last `checkpoint` on death (no scene reload). `ScoreKeeper.loseLife` at
  0 lives emits `lost`.
- WIN: touching the `goal` group sets the score to `scoreToWin` (100000) →
  `won`. Coins (10) + gems (50) are score flavour only; they can't reach the
  threshold, so only the flag wins.
- MOVING-PLATFORM CARRY: the kinematic controller doesn't inherit platform
  velocity, so the player has a `Feet` Area sensor that remembers the `platform`
  it stands on; `PlayerController` adds that platform's per-frame delta so you
  ride it.
- ENGINE ACCESS IS DEFERRED: `this.engine` throws in `onReady` during
  `loadScene` (scene not attached yet) — read scene config (gravity) lazily on
  the first `fixedUpdate`. `getNode`/signal wiring is fine in `onReady`.
- `FollowCam` does follow + screen-shake in ONE behavior (one script per node):
  `PlayerController.shake()` calls into it on stomps/landings/hits.
- Node uids are omitted in the JSON — the loader generates them. `window.game`
  exposes the Game handle in the console.
