# Structure — platformer-2d (Incanto)

## `index.html`

Full-window page: inline favicon, loading overlay (removed once the game boots),
one `#game` canvas, an on-screen `#btn-jump` button (mobile parity), the
iframe-embed GAME_SIZE script, and the module entry.

## `src/game.scene.json`

THE whole level (open it with `npx incanto-editor`):

- scene header — `environment.background`, `viewport` (`design [960,540]`, `fit:
  expand`), `physics.gravity [0,1800]`, `move`/`jump` input, and the
  `knight`/`goblin` spritesheets + `coin`/`gem` texture `assets` (urls injected
  in `App.tsx`).
- `Game` root — `ScoreKeeper` (`scoreToWin 100000`, `lives 3`).
- Parallax castle backdrop — `Sky`/`Sun`/`Cloud1–3` (far), `CastleFar` band +
  `TowerFarA–C`, `CastleNear` band + `BattlementA/B` — `ColorRect2D` +
  `ParallaxLayer` (factor 0.6–0.96, higher = farther).
- `GroundA–D` — `StaticBody2D` stone (brown body + green `Moss` cap) with GAPS
  (pits) between them.
- `Plat1–4` — static floating platforms; `MovePlat` (`Patrol` pingpong, group
  `platform`) ferries the first pit; `Lift` (`Oscillate` vertical, group
  `platform`) crosses the spike pit.
- `Spikes1/2` (group `hazard`) and `DeathPlane` (group `pit`, far below).
- `Checkpoint` (group `checkpoint`, blue flag) and `Goal` (group `goal`, gold
  flag + gate).
- `Gob1–3` — Node2D (group `enemy`) + `Patrol`; children `Skin`
  (`AnimatedSprite2D` goblin) + `AI` (`GoblinSkin`).
- `Coins/Coin1–12` — Area2D `Pickup` (value 10) with a bobbing `coin` sprite
  child; `Gem1/2` — Area2D `Pickup` (value 50) with a bobbing `gem` sprite.
- `Player` — `CharacterBody2D` (group `player`, capsule) + `PlayerController`;
  children `Skin` (`AnimatedSprite2D` knight idle/run/jump) + `Feet` (Area2D
  sensor for moving-platform carry).
- `Camera` — `Camera2D` (`current`) + `FollowCam` (look-ahead, clamped `limits`,
  screen-shake).
- `SfxJump/Coin/Stomp/Hurt/Checkpoint/Win/Lose` — `AudioPlayer` SFX presets
  (PlayerController plays jump/stomp/hurt/checkpoint directly; coins/win/lose via
  `connections`).
- `HUD` — UILayer (top-left, `HudUpdater`): Coin / Hearts / Lives / Hint Labels.
  `BannerLayer` — UILayer (center) win/lose `Banner`.
- `connections` — each coin/gem `collected → ScoreKeeper.addScore` (+ SFX); root
  `won/lost → SfxWin/SfxLose`. (Stomp/hit/checkpoint/respawn/win-trigger are
  resolved by `PlayerController`, not connections.)

## `src/behaviors.ts`

- `PlayerController` — the heart: run/gravity/coyote/buffer/double-jump/variable
  height, stomp + bounce, side-hit knockback, hearts+lives, checkpoint respawn,
  moving-platform carry (via the `Feet` sensor), sprite clip + facing + i-frame
  blink. All enemy/hazard/pit/checkpoint/goal contact is AABB against groups in
  `resolveContacts()`.
- `GoblinSkin` — face the patrol heading + play the goblin walk clip.
- `FollowCam` — follow (look-ahead + smoothing + world clamp) + `shake()`.
- `ParallaxLayer` — re-anchor a backdrop to `cameraX · factor` for depth.
- `HudUpdater` — score/hearts/lives Labels + win/lose banner.

## `src/main.tsx` and `src/App.tsx`

Boot: inject the four built-in asset urls, `createGame2D({ canvas, scene,
behaviors })`, optional `engine.music`, the JUMP button, remove the loader,
expose `window.game`.

## `verify.ts`

Browserless proof via `incanto/test` `runScript`: run+jump (off the ground and
back), coin scoring, STOMP (goblin removed + scored), side-hit costs a heart,
checkpoint respawn (die → back at the flag), WIN (goal → `won`), LOSE (pit with
the last life → `lost`). Run with `bun run verify`.

## `docs/project-2d-rules.md`

The vibe-coding rules: read the skills, JSON structure, built-ins first, omit
uids, declared assets, y-down space, collider props, GROUP-DRIVEN centralised
damage (§8), deferred engine access (§11), debug draw, delta discipline, verify
like a user.

- `CourseFloor`, `Trampoline`, `Bouncer`, `Ice`, `Sled`, `Hound`, `Perch` — the harness's course for
  the 2D surfaces and the ground chaser.
