# Phaser Admin & Architecture Guide

This guide is the deep technical reference for Phaser 4 — aimed at engineers integrating Phaser into a larger application, architects evaluating it, build engineers shipping production games, and contributors working on the framework itself. It documents the internal architecture, the complete configuration surface, the build and distribution model, security considerations, and the structure of the source tree.

For end-user, gameplay-focused documentation (how to create sprites, handle input, play sounds), see the companion **[User Guide](phaserjs__phaser__UserGuide.md)**. This guide assumes you already understand the basics.

> [!NOTE]
> This guide documents **Phaser 4.1.0** ("Salusa"), a major release built on an entirely new WebGL renderer. Phaser is MIT-licensed and commercially developed by Phaser Studio Inc. The only runtime dependency is `eventemitter3`.

---

## Table of Contents

1. [Executive Summary](#executive-summary)
2. [System Architecture](#system-architecture)
3. [Requirements & Compatibility](#requirements--compatibility)
4. [Configuration Mastery](#configuration-mastery)
5. [Build & Distribution](#build--distribution)
6. [Integrating Phaser into an App](#integrating-phaser-into-an-app)
7. [The Rendering Architecture](#the-rendering-architecture)
8. [Subsystem Reference](#subsystem-reference)
9. [Repository Structure](#repository-structure)
10. [Security Analysis](#security-analysis)
11. [Performance & Operations](#performance--operations)
12. [Code Quality & Conventions](#code-quality--conventions)
13. [Testing Strategy](#testing-strategy)
14. [Known Issues & Tech Debt](#known-issues--tech-debt)
15. [Extending Phaser](#extending-phaser)
16. [Contributing](#contributing)
17. [Quick Reference](#quick-reference)

---

## Executive Summary

**What Phaser is.** Phaser is a 2D HTML5 game framework that renders with WebGL (primary) or Canvas (fallback) across desktop and mobile browsers. It provides a complete game-development stack: a scene-based architecture, a parallel asset loader, two physics engines (Arcade and Matter.js), animation, tweening, input (keyboard/mouse/touch/gamepad), audio (Web Audio + HTML5), cameras, tilemaps, particles, and a comprehensive math/geometry library.

**Why it's chosen.**

- **Maturity:** Over a decade of active development; tens of thousands of shipped games.
- **Single codebase, many targets:** Web, plus native wrapping for iOS/Android/Steam, YouTube Playables, Discord Activities, Twitch overlays.
- **Framework-agnostic:** Drops into React, Vue, Angular, Svelte, or vanilla JS/TS.
- **Tiny footprint:** 345 KB gzipped for the full minified build.
- **First-class TypeScript:** Generated `.d.ts` definitions cover the entire API.
- **Zero hard runtime dependencies** beyond `eventemitter3`.

**Architectural philosophy.** Phaser is built around a single `Game` controller that owns a set of global managers, with `Scene` instances as the primary unit of composition. The filesystem-of-modules pattern is consistent: each subsystem lives in its own folder under `src/`, exports a namespace, and is assembled into the top-level `Phaser` global. Game objects are constructed by **mixing component objects** into prototypes rather than deep inheritance — a flat, composable design.

**The v4 leap.** Phaser 4 replaced the entire v3 rendering pipeline with a node-based **RenderNode** architecture that centralizes WebGL state management, handles context loss gracefully, and is significantly faster. It also unified the FX and Mask systems into a single **Filter** system, overhauled tinting, and added GPU-accelerated game objects (`SpriteGPULayer`, `TilemapGPULayer`) capable of rendering millions of elements.

| Attribute | Value |
|---|---|
| Version | 4.1.0 ("Salusa") |
| License | MIT |
| Language | JavaScript (ES5-targeted source, built with Webpack) |
| Runtime dependency | `eventemitter3` ^5.0.4 |
| Renderers | WebGL (primary), Canvas (fallback/deprecated) |
| Physics | Arcade (AABB), Matter.js (rigid-body) |
| Module formats | UMD (`dist/phaser.js`), ESM (`dist/phaser.esm.js`) |
| Source size | ~10 M chars across ~3,500 files |
| Maintainer | Phaser Studio Inc. |

---

## System Architecture

### High-level structure

A running Phaser game is a single `Game` instance owning global managers, driving one or more `Scene` instances each frame.

```
                          new Phaser.Game(config)
                                   │
                    ┌──────────────┴───────────────┐
                    │         Phaser.Game            │
                    │  (the single root controller)  │
                    └──────────────┬─────────────────┘
                                   │ owns global managers
   ┌───────────────┬──────────────┼──────────────┬───────────────┐
   │               │              │              │               │
┌──▼───┐     ┌─────▼─────┐  ┌─────▼─────┐  ┌─────▼─────┐   ┌──────▼──────┐
│Render│     │  Texture  │  │  Sound    │  │  Input    │   │  TimeStep   │
│ -er  │     │  Manager  │  │  Manager  │  │  Manager  │   │ (game loop) │
└──────┘     └───────────┘  └───────────┘  └───────────┘   └──────┬──────┘
                                                                  │ drives
   ┌───────────────┬──────────────┬──────────────┐               │
┌──▼───┐    ┌──────▼──────┐  ┌─────▼─────┐  ┌─────▼─────┐         │
│Anim  │    │   Cache     │  │   Scale   │  │  Plugin   │         │
│ Mgr  │    │  Manager    │  │  Manager  │  │  Manager  │         │
└──────┘    └─────────────┘  └───────────┘  └───────────┘         │
                                                                  │
                    ┌──────────────────────────────┐             │
                    │       SceneManager            │◄────────────┘
                    │  (ordered list of Scenes)     │   step(time, delta)
                    └──────────────┬────────────────┘
                                   │ each scene has its own:
              ┌────────────────────┼─────────────────────┐
        ┌─────▼─────┐       ┌──────▼──────┐        ┌──────▼──────┐
        │   Scene   │       │   Scene     │        │   Scene     │
        │  Systems  │       │  Systems    │        │  Systems    │
        │ add/load/ │       │ cameras/    │        │ input/      │
        │ physics/  │       │ tweens/time │        │ tweens/...  │
        │ tweens... │       └─────────────┘        └─────────────┘
        └─────┬─────┘
              │ display list
        ┌─────▼─────────────────────────────┐
        │  Game Objects (Sprite, Image,     │
        │  Text, Container, Particles, ...) │
        └───────────────────────────────────┘
```

### The two-tier manager model

Phaser separates **global** managers (owned by `Game`, shared by all scenes) from **scene-local** systems (one per scene):

| Global (on `game.*`) | Scene-local (on `this.*` via injection) |
|---|---|
| `renderer`, `textures`, `cache` | `add` (factory), `make` (creator) |
| `anims` (AnimationManager) | `load` (LoaderPlugin) |
| `sound` (SoundManager) | `cameras` (CameraManager) |
| `input` (InputManager) | `input` (InputPlugin — scene wrapper) |
| `scene` (SceneManager) | `tweens`, `time`, `data` |
| `scale` (ScaleManager) | `physics` / `matter` (if configured) |
| `plugins` (PluginManager) | `events`, `children` (display list) |
| `registry` (global DataManager) | `lights` |
| `loop` (TimeStep) | `scene` (ScenePlugin) |

The **Scene Injection Map** (`src/scene/InjectionMap.js`) defines which systems are mounted onto a Scene instance and under what property name. This is configurable per scene via the scene config's `map` property.

### Frame execution model

The `TimeStep` (`src/core/TimeStep.js`) requests animation frames and calls `Game.step(time, delta)`:

```
TimeStep (requestAnimationFrame)
   │
   ▼
Game.step(time, delta)
   ├─ emit PRE_STEP                     (input/sound managers update)
   ├─ emit STEP                          (user-land + plugins)
   ├─ SceneManager.update(time, delta)   (each active scene, REVERSE order)
   │     └─ Scene.sys.step → scene.update()
   ├─ emit POST_STEP
   ├─ renderer.preRender()
   ├─ emit PRE_RENDER
   ├─ SceneManager.render(renderer)      (each visible scene, FORWARD order)
   ├─ renderer.postRender()
   └─ emit POST_RENDER
```

Scenes **update in reverse array order** (top scene first, for input priority) and **render in forward order** (top scene last, so it draws on top). This asymmetry is intentional and is how overlay/UI scenes get input priority while appearing on top.

There is a separate `headlessStep` for the HEADLESS renderer that skips actual rendering but still emits `PRE_RENDER`/`POST_RENDER`.

### Boot sequence

From `Game.boot()` (`src/core/Game.js`):

1. Constructor parses config into `Config`, instantiates every global manager.
2. Waits for `DOMContentLoaded`, then `boot()`:
   - `config.preBoot(game)` callback.
   - `scale.preBoot()`.
   - `CreateRenderer(game)` — picks WebGL/Canvas/Headless, creates the canvas.
   - `CreateDOMContainer(game)` (if `dom.createContainer`).
   - `DebugHeader(game)` — the styled console banner.
   - `AddToDOM(canvas, parent)`.
   - Emits `BOOT`.
3. When `TextureManager` emits `READY` → `texturesReady()` emits `READY`, calls `start()`.
4. `start()` begins the `TimeStep` loop, installs the `VisibilityHandler`, runs `config.postBoot(game)`.

---

## Requirements & Compatibility

### Runtime requirements

- **A modern browser with WebGL** (Canvas is a deprecated fallback). WebGL is effectively baseline today.
- **The DOM.** Even the HEADLESS renderer requires a DOM environment — Phaser is **not** designed to run server-side in Node.
- **No build tooling at runtime** — the distributed bundles are self-contained.

### Development requirements (building from source)

From `package.json`:

| Tool | Purpose |
|---|---|
| Webpack 5 + webpack-cli | Bundling (`config/webpack.config.js`) |
| Terser plugin | Minification |
| ESLint 10 (+ `eslint-plugin-es5`) | Linting; enforces ES5-compatible source |
| Vitest 4 | Unit testing |
| jsdom | DOM simulation for tests |
| TypeScript 6 + jsdoc + dts-dom | Generating `.d.ts` type definitions from JSDoc |
| node-sloc | Source line counting |

The source is authored to be **ES5-compatible** (enforced by `eslint-plugin-es5`) and transpiled/bundled by Webpack. The single runtime dependency is `eventemitter3`.

### Platform targets

Games built with Phaser run on:

- Desktop browsers (Chrome, Firefox, Safari, Edge).
- Mobile browsers (iOS Safari, Android Chrome).
- Native app wrappers (Cordova/Capacitor → iOS/Android, Electron/NW.js → desktop).
- Steam (via Electron/NW.js).
- YouTube Playables, Discord Activities, Twitch overlays.

> [!NOTE]
> The Loader recognizes `file://` and `capacitor://` as local schemes by default (`loader.localScheme`), which matters for Cordova/Capacitor wrapping where assets are served from the app bundle.

---

## Configuration Mastery

The full `GameConfig` is parsed by `src/core/Config.js` into a read-only `Config` instance, available as `game.config`. Every value below is resolved with the default shown if not provided. Some properties can be set at the top level **or** nested in a sub-object (e.g. `width` or `scale.width`); the nested sub-object takes priority.

### Top-level properties

| Property | Type | Default | Description |
|---|---|---|---|
| `type` | number | `Phaser.AUTO` (0) | Renderer: `AUTO`, `CANVAS`, `WEBGL`, `HEADLESS` |
| `width` | number\|string | `1024` | Canvas width (overridden by `scale.width`) |
| `height` | number\|string | `768` | Canvas height (overridden by `scale.height`) |
| `zoom` | number | `1` | Canvas zoom multiplier (overridden by `scale.zoom`) |
| `parent` | HTMLElement\|string\|null | `undefined` | Parent element/id. `undefined` = body, `null` = don't inject |
| `canvas` | HTMLCanvasElement | `null` | Use your own canvas element |
| `context` | Rendering context | `null` | Use your own rendering context |
| `canvasStyle` | string | `null` | CSS applied to the canvas |
| `customEnvironment` | boolean | `false` | Skip feature detection (non-browser). Forbids `AUTO` |
| `scene` | SceneType\|SceneType[] | `null` | Scene class or array |
| `seed` | string[] | random | Seed for `Phaser.Math.RND` |
| `title` / `url` / `version` | string | `''` | Console banner metadata |
| `autoFocus` | boolean | `true` | Auto-focus window on boot and mousedown |
| `stableSort` | number\|boolean | `-1` | `-1` auto-detect, `0` built-in sort, `1` native ES2019 sort |
| `disableContextMenu` | boolean | `false` | Disable right-click menu |
| `backgroundColor` | string\|number | `0x000000` | Canvas background (ignored if `transparent`) |
| `banner` | boolean\|object | shown | `false` to hide the console banner |

### `scale` sub-config

| Property | Type | Default | Description |
|---|---|---|---|
| `width` / `height` | number\|string | `1024` / `768` | Base game dimensions |
| `zoom` | number | `1` | Zoom; constants `NO_ZOOM`/`ZOOM_2X`/`ZOOM_4X`/`MAX_ZOOM` |
| `parent` | HTMLElement\|string | `undefined` | DOM parent |
| `mode` | number | `NONE` (0) | Scale mode (see below) |
| `expandParent` | boolean | `true` | Adjust parent CSS height to 100% |
| `autoRound` | boolean | `false` | Round display/style sizes |
| `autoCenter` | number | `NO_CENTER` (0) | Centering mode |
| `resizeInterval` | number | `500` | ms between browser-size checks |
| `fullscreenTarget` | HTMLElement\|string | `null` | Element for fullscreen |
| `min` / `max` | `{width, height}` | `{0,0}` | Min/max canvas dimensions (0 = none) |
| `snap` | `{width, height}` | `{0,0}` | Snap canvas size to multiples |

**Scale modes:** `NONE` (0), `WIDTH_CONTROLS_HEIGHT` (1), `HEIGHT_CONTROLS_WIDTH` (2), `FIT` (3), `ENVELOP` (4), `RESIZE` (5), `EXPAND` (6, new in v4).
**Center modes:** `NO_CENTER` (0), `CENTER_BOTH` (1), `CENTER_HORIZONTALLY` (2), `CENTER_VERTICALLY` (3).

### `render` sub-config

These can be set top-level or under `render` (the sub-object wins).

| Property | Type | Default | Description |
|---|---|---|---|
| `antialias` | boolean | `true` | Linear interpolation for scaled/rotated textures |
| `antialiasGL` | boolean | `true` | Antialias on WebGL context creation |
| `pixelArt` | boolean | `false` | Sets `antialias:false`, `roundPixels:true` |
| `smoothPixelArt` | boolean | `false` | WebGL: blocky pixels with smoothed edges (new in v4) |
| `roundPixels` | boolean | `false` | Snap texture objects to integer positions (was `true` in v3) |
| `transparent` | boolean | `false` | Transparent canvas (forces bg to rgba(0,0,0,0)) |
| `clearBeforeRender` | boolean | `true` | Clear canvas each frame |
| `preserveDrawingBuffer` | boolean | `false` | Preserve WebGL buffers between frames |
| `premultipliedAlpha` | boolean | `true` | Pre-multiplied alpha in WebGL |
| `desynchronized` | boolean | `false` | Desynchronized context |
| `failIfMajorPerformanceCaveat` | boolean | `false` | Abort WebGL if browser reports poor perf |
| `powerPreference` | string | `'default'` | `'high-performance'`, `'low-power'`, `'default'` |
| `batchSize` | number | `16384` | Max quads per WebGL batch |
| `maxTextures` | number | `-1` | Max GPU textures (`-1` = all available) |
| `maxLights` | number | `10` | Max lights visible per camera |
| `autoMobileTextures` | boolean | `true` | Restrict to 1 texture/batch on iOS/Android |
| `selfShadow` | boolean | `false` | Self-shadowing on lit textured objects |
| `pathDetailThreshold` | number | `1` | Graphics WebGL path point-combining threshold (new in v4) |
| `skipUnreadyShaders` | boolean | `false` | Skip objects whose shader is still compiling (new in v4) |
| `mipmapFilter` | string | `''` | Mipmap mag filter (`'NEAREST'`, `'LINEAR'`, ...) |
| `mipmapRegeneration` | boolean | `false` | Regenerate framebuffer mipmaps (expensive) |
| `renderNodes` | object | `{}` | Custom WebGL render nodes (new in v4) |

> [!WARNING]
> `mipmapRegeneration` is expensive (10+ microseconds per texture) and affects filters and DynamicTextures. Leave it `false` unless you specifically need framebuffer mipmaps.

### `input` sub-config

| Property | Default | Description |
|---|---|---|
| `keyboard` | `true` | Enable Keyboard plugin |
| `keyboard.target` | `window` | DOM target for keyboard events |
| `keyboard.capture` | `[]` | Key codes to `preventDefault` |
| `mouse` | `true` | Enable Mouse plugin |
| `mouse.target` | game canvas | DOM target for mouse events |
| `mouse.preventDefault{Down,Up,Move,Wheel}` | `true` | preventDefault toggles |
| `touch` | device support | Enable Touch plugin |
| `touch.capture` | `true` | Call preventDefault on touch |
| `activePointers` | `1` | Number of Pointer objects created |
| `smoothFactor` | `0` | Pointer movement smoothing |
| `windowEvents` | `true` | Listen for input on `window` |
| `gamepad` | `false` | Enable Gamepad plugin |

### `loader` sub-config

| Property | Default | Description |
|---|---|---|
| `baseURL` | `''` | Prepended to all relative URLs |
| `path` | `''` | Prepended after baseURL |
| `maxParallelDownloads` | 32 (6 on Android) | Concurrent downloads |
| `crossOrigin` | `undefined` | CORS setting (`'anonymous'`, etc.) |
| `responseType` | `''` | XHR response type |
| `async` | `true` | Async XHR |
| `user` / `password` | `''` | XHR credentials |
| `timeout` | `0` | XHR timeout (ms) |
| `maxRetries` | `2` | Retries per failed file (was `0` in early v3) |
| `withCredentials` | `false` | XHR withCredentials |
| `imageLoadType` | `'XHR'` | `'XHR'` or `'HTMLImageElement'` |
| `localScheme` | `['file://','capacitor://']` | Schemes treated as local |

### Other sub-configs

| Key | Source typedef | Purpose |
|---|---|---|
| `physics` | `PhysicsConfig` | `default: 'arcade'\|'matter'` plus per-system config |
| `fps` | `FPSConfig` | `min`, `target`, `limit`, `forceSetTimeOut`, `smoothStep`, `deltaHistory`, `panicMax` |
| `audio` | `AudioConfig` | `disableWebAudio`, `noAudio`, `context` |
| `callbacks` | `CallbacksConfig` | `preBoot(game)`, `postBoot(game)` |
| `dom` | `DOMContainerConfig` | `createContainer`, `pointerEvents` |
| `plugins` | `PluginObject` | global/scene/default plugin registration |
| `images` | `ImagesConfig` | `default`, `missing`, `white` base64 textures |
| `filters.glow` | — | `quality` (10), `distance` (10) defaults for the Glow filter |

> [!IMPORTANT]
> `Config.js` checks for `window.FORCE_WEBGL` and `window.FORCE_CANVAS` globals at the end of parsing and overrides `type` accordingly. This is intended for development/testing — be aware of it if a build behaves unexpectedly.

---

## Build & Distribution

### npm package entry points

From `package.json`:

```json
{
  "main": "./src/phaser.js",
  "types": "./types/phaser.d.ts",
  "browser": "./dist/phaser.js",
  "module": "./dist/phaser.esm.js",
  "exports": {
    ".": {
      "types": "./types/phaser.d.ts",
      "import": "./dist/phaser.esm.js",
      "require": "./dist/phaser.js",
      "default": "./dist/phaser.esm.js"
    }
  }
}
```

Bundlers resolve the ESM build (`dist/phaser.esm.js`) via `import`, the UMD build (`dist/phaser.js`) via `require`, and the `.d.ts` for types.

### Build entry points (source)

`src/` contains multiple entry points that produce different bundles by including or excluding subsystems:

| Entry file | Bundle contents |
|---|---|
| `src/phaser.js` | Full build — all features, both physics engines |
| `src/phaser-arcade-physics.js` | Excludes Matter.js |
| `src/phaser-no-physics.js` | Excludes both physics engines |
| `src/phaser-core.js` | Minimal core — basic game objects only |
| `src/phaser-esm.js` | ESM build with named + default exports |

Each entry assembles the `Phaser` namespace object from subsystem `require()`s, merges in `CONST`, and assigns `global.Phaser`. Conditional features are gated behind Webpack `DefinePlugin` flags (`FEATURE_SOUND`, `WEBGL_RENDERER`, `CANVAS_RENDERER`, `WEBGL_DEBUG`) that are inlined at build time so dead code is stripped.

### npm scripts

| Script | Command | Purpose |
|---|---|---|
| `build` | `webpack --config config/webpack.config.js` | Development build |
| `watch` | `webpack --watch ...` | Rebuild on change |
| `dist` | `webpack --config config/webpack.dist.config.js` | Production distribution |
| `lint` / `lintfix` | `eslint --config .eslintrc.json "src/**/*.js"` | Lint (and auto-fix) |
| `bundleshaders` | `node scripts/bundle-shaders.js` | Bundle GLSL shaders |
| `tsgen` | `jsdoc -c jsdoc-tsd.conf.json` | Generate TypeScript defs from JSDoc |
| `test-ts` | `tsc --build tsconfig.json` | Compile-check the generated types |
| `test` / `test:watch` | `vitest run` / `vitest` | Run unit tests |
| `sloc` | `node-sloc "./src" --include-extensions "js"` | Count source lines |

### The TypeScript definition pipeline

Phaser's `.d.ts` files are **generated from JSDoc comments** (not hand-written), which is why the readable source is so large. The pipeline: `build-tsgen` compiles the custom tsgen tool (`scripts/tsgen/`), `tsgen` runs jsdoc with a TSD plugin to emit `types/phaser.d.ts`, and `test-ts` compiles a test project against the generated types to catch regressions.

> [!NOTE]
> Because types are JSDoc-derived, the accuracy of the TypeScript API depends entirely on the JSDoc annotations in source. Several open issues (e.g. #7249, #7248, #7260) concern improving the strictness and literal-typing of the generated definitions.

---

## Integrating Phaser into an App

### Framework integration pattern

Phaser is a self-contained DOM widget — it owns a `<canvas>`. The integration pattern with React/Vue/etc. is to mount the game into a container element and tear it down on unmount:

```js
// React example (concept)
useEffect(() => {
    const game = new Phaser.Game({
        type: Phaser.AUTO,
        parent: containerRef.current,
        scene: [GameScene]
    });
    return () => game.destroy(true);   // clean up on unmount
}, []);
```

> [!CAUTION]
> `game.destroy(removeCanvas, noReturn)` is **asynchronous** — it flags the game for destruction on the next frame. Listen for `Phaser.Core.Events.DESTROY` to know when cleanup completed. Pass `noReturn: true` only if you will never create another Phaser instance on that page; it releases additional internal references that prevent re-creation.

### Communicating between app and game

- **App → game:** Keep a reference to the `game` instance; reach scenes via `game.scene.getScene('Key')` and call public methods, or push data into `game.registry`.
- **Game → app:** Emit events on `game.events` or a scene's `events`, which your framework code listens to.
- **Shared state:** The global `registry` (a DataManager) is the cleanest cross-boundary store.

### SPA / WebAudio context reuse

For single-page apps that recreate the game without a full page reload, reuse the audio context to avoid leaking suspended contexts:

```js
const config = { audio: { context: existingAudioContext } };
// or at runtime: game.sound.setAudioContext(newContext);   // WebAudio only
```

### Lifecycle and visibility

`game.events` emits `'blur'`, `'focus'`, `'hidden'`, `'visible'`, `'pause'`, `'resume'`. The `VisibilityHandler` auto-pauses the loop when the tab is hidden. The SoundManager's `pauseOnBlur` (default `true`) auto-pauses audio on blur. iOS 17/18+ audio interruptions are handled via `context.suspend()`/`resume()` on visibility events.

---

## The Rendering Architecture

This is the defining change in Phaser 4 and the most important section for anyone doing custom rendering or evaluating performance.

### RenderNodes replace Pipelines

In v3, a `Pipeline` was a rendering subsystem that often held multiple responsibilities and managed WebGL state independently — leading to conflicts (one pipeline could corrupt another's assumptions). In v4, the `RenderNodeManager` (on the WebGL renderer) owns a graph of **RenderNodes**, each handling exactly one task via a `run()` method. Some nodes also have a `batch()` method to accumulate state from multiple sources before drawing.

Game objects reference nodes through **role-based maps**:

```js
gameObject.defaultRenderNodes   // built-in nodes per role
gameObject.customRenderNodes    // per-role overrides
gameObject.renderNodeData       // data keyed by node name

// Roles: 'Submitter' (drives other roles per element),
//        'Transformer' (vertex coordinates), 'Texturer' (textures)
gameObject.setRenderNodeRole('Submitter', 'MyCustomSubmitter', { customProp: 42 });
gameObject.setRenderNodeRole('Submitter', null);   // revert to default
```

**Built-in node categories:**

- **Batch handlers** (multi-object draws): `BatchHandlerQuad`, `BatchHandlerQuadSingle`, `BatchHandlerTileSprite`, `BatchHandlerTriFlat`, `BatchHandlerPointLight`, `BatchHandlerStrip`.
- **Submitters:** `SubmitterQuad`, `SubmitterTile`, `SubmitterTileSprite`, `SubmitterSpriteGPULayer`, `SubmitterTilemapGPULayer`.
- **Transformers:** `TransformerImage`, `TransformerStamp`, `TransformerTile`, `TransformerTileSprite`.
- **Texturers:** `TexturerImage`, `TexturerTileSprite`.
- **Filters:** `FilterBlur`, `FilterGlow`, `FilterColorMatrix`, `FilterBlend`, `FilterMask`, `FilterPixelate`, `FilterShadow`, `FilterVignette`, `FilterWipe`, and ~20 more.
- **Compositing/utility:** `Camera`, `FillRect`, `FillPath`, `DrawLine`, `StrokePath`, `ShaderQuad`, `ListCompositor`, `RebindContext`, `YieldContext`, `DynamicTextureHandler`.

Performance benefits in v4: quads now use index buffers (≈⅓ fewer vertex uploads), smarter multi-texture batching (fewer batch breaks on mobile), just-in-time rendering (nothing hits the GPU until necessary), and removal of `genericVertexBuffer`/`genericVertexData` (~16 MB RAM/VRAM freed).

> [!CAUTION]
> Do **not** make direct WebGL `gl` calls in a v4 game. The internal `WebGLGlobalWrapper` tracks state; raw `gl` calls desync it and cause unpredictable behavior. If you need direct WebGL access, use an `Extern` game object, which resets state when it finishes.

### Render flow via RenderSteps

The `RenderSteps` component lets game objects inject custom logic into their render. Steps are stored in `_renderSteps` and executed via `renderWebGLStep()`; the first step is responsible for invoking subsequent steps. The Filters system is built on RenderSteps — it defers and controls the `renderWebGL` flow this way.

### The Filter system

Filters unify v3's FX and Masks. A filter takes an input image and produces an output via a shader pass, and applies to any game object or camera (WebGL only). Two lists:

- `filters.internal` — runs **before** the camera transform (object-local space, cheaper).
- `filters.external` — runs **after** (screen space, full resolution).

```js
sprite.enableFilters();
sprite.filters.internal.addGlow(0xff00ff, 4, 0, 1);
sprite.filters.external.addBlur(0, 2, 2, 1);
sprite.filters.internal.addMask(maskImage);   // BitmapMask replacement
camera.filters.external.addColorMatrix().grayscale();
```

Built-in filters: Barrel, Blend, Blocky, Blur (Low/Med/High), Bokeh, ColorMatrix, CombineColorMatrix, Displacement, Glow, GradientMap, ImageLight, Key (chroma), Mask, NormalTools, PanoramaBlur, ParallelFilters, Pixelate, Quantize, Sampler, Shadow, Threshold, Vignette, Wipe.

### Renderer selection

`CreateRenderer` (`src/core/CreateRenderer.js`) resolves the renderer:

1. `customEnvironment` or a provided `canvas` with `type: AUTO` throws (must specify explicit type).
2. `AUTO` → WebGL if `Features.webGL`, else Canvas.
3. `WEBGL` → throws if WebGL unavailable (**no fallback**).
4. The build's `WEBGL_RENDERER`/`CANVAS_RENDERER` DefinePlugin flags determine which renderer classes are even included.

### Canvas renderer status

Canvas is **deprecated** in v4 (still functional). It can't do any of v4's WebGL techniques. Its one remaining advantage — 27 blend modes vs WebGL's 4 native (NORMAL, ADD, MULTIPLY, SCREEN) — is now recoverable in WebGL via the new `Blend` filter (with indirection through a CaptureFrame/DynamicTexture). New projects should use WebGL.

---

## Subsystem Reference

A map of the major subsystems, their entry namespace, and the source folder that owns them.

| Namespace | Folder | Responsibility |
|---|---|---|
| `Phaser.Core` | `src/core/` | Game, Config, TimeStep, CreateRenderer, VisibilityHandler |
| `Phaser.Scenes` | `src/scene/` | Scene, SceneManager, ScenePlugin, Systems, InjectionMap |
| `Phaser.GameObjects` | `src/gameobjects/` | All game objects, factories, creators, component mixins |
| `Phaser.Loader` | `src/loader/` | LoaderPlugin, File, 30+ filetype loaders |
| `Phaser.Textures` | `src/textures/` | TextureManager, Texture, Frame, parsers |
| `Phaser.Cache` | `src/cache/` | CacheManager and typed sub-caches |
| `Phaser.Animations` | `src/animations/` | AnimationManager, Animation, AnimationState |
| `Phaser.Tweens` | `src/tweens/` | TweenManager, Tween, TweenChain, builders, easing |
| `Phaser.Input` | `src/input/` | InputManager, InputPlugin, Pointer, Keyboard, Gamepad |
| `Phaser.Physics.Arcade` | `src/physics/arcade/` | World, Body, StaticBody, Collider, groups |
| `Phaser.Physics.Matter` | `src/physics/matter-js/` | Matter.js wrapper + bundled engine |
| `Phaser.Cameras` | `src/cameras/` | CameraManager, Camera, effects, controls |
| `Phaser.Sound` | `src/sound/` | WebAudio/HTML5/NoAudio managers and sounds |
| `Phaser.Tilemaps` | `src/tilemaps/` | Tilemap, layers, parsers (Tiled/CSV/Impact), GPU layer |
| `Phaser.Scale` | `src/scale/` | ScaleManager and scale/center/zoom constants |
| `Phaser.Math` | `src/math/` | Vectors, matrices, RNG, easing, interpolation |
| `Phaser.Geom` | `src/geom/` | Circle, Ellipse, Line, Polygon, Rectangle, Triangle, intersects |
| `Phaser.Display` | `src/display/` | Color, masks, bounds, align, canvas utils |
| `Phaser.Curves` | `src/curves/` | Bezier, spline, path curves |
| `Phaser.Time` | `src/time/` | Clock, TimerEvent |
| `Phaser.Data` | `src/data/` | DataManager (registry + per-scene data) |
| `Phaser.Filters` | `src/filters/` | Filter definitions (the v4 FX/Mask system) |
| `Phaser.Renderer` | `src/renderer/` | WebGL + Canvas renderers, RenderNodes, shaders, wrappers |
| `Phaser.Plugins` | `src/plugins/` | PluginManager, PluginCache, BasePlugin, ScenePlugin |
| `Phaser.Structs` | `src/structs/` | List, ProcessQueue, RTree, Size |
| `Phaser.Utils` | `src/utils/` | Class, object/array/string helpers, base64 |
| `Phaser.Actions` | `src/actions/` | Bulk operations over arrays of game objects |
| `Phaser.Events` | `src/events/` | The shared EventEmitter export |

### Game Object component model

Game objects are composed by **mixing component objects** into the prototype rather than inheriting deeply. The base `GameObject` mixes in `Filters` and `RenderSteps`; `Sprite` and `Image` share an identical set:

```
Alpha, BlendMode, Depth, Flip, GetBounds, Lighting, Mask, Origin,
RenderNodes, ScrollFactor, Size, TextureCrop, Tint, Transform, Visible
```

The difference between Sprite and Image is just the `AnimationState` (`sprite.anims`) and a render-specific mixin. The authoritative reference for a method like `setAlpha()` is its component source file (`src/gameobjects/components/Alpha.js`), not the game object class. The complete component list is in `src/gameobjects/components/index.js`.

New v4 components: `Lighting`, `RenderSteps`, `RenderNodes`.

### Physics: Arcade internals

| File | Responsibility |
|---|---|
| `ArcadePhysics.js` | The `this.physics` scene plugin |
| `World.js` | Simulation: bodies, gravity, bounds, colliders, RTree, step loop |
| `Body.js` | Dynamic body — velocity, acceleration, bounce, drag, mass, collision state |
| `StaticBody.js` | Immovable body with optimized RTree lookup |
| `Factory.js` | `this.physics.add.*` |
| `Collider.js` | Persistent collision/overlap with callbacks |
| `PhysicsGroup.js` / `StaticPhysicsGroup.js` | Groups with per-member body defaults |

The World uses an RTree spatial index for dynamic bodies by default (`useTree`), which becomes expensive to rebuild beyond ~5,000 dynamic bodies — disable it for very large simulations. Fixed timestep (`fixedStep: true`, `fps: 60`) is the default; collision categories use 32-bit masks (max 32 categories).

### Sound: backend selection

`SoundManagerCreator.create()` picks the backend: `NoAudioSoundManager` if disabled/unsupported, else `WebAudioSoundManager` (preferred — precise timing, panning, spatial audio), else `HTML5AudioSoundManager`. There is one manager per game.

---

## Repository Structure

```
phaser/
├── src/                    Core source (~3,500 files, ES5-authored)
│   ├── phaser.js           Full build entry point
│   ├── phaser-*.js         Alternate build entry points
│   ├── const.js            Global constants (VERSION, AUTO/CANVAS/WEBGL/HEADLESS)
│   ├── core/               Game, Config, TimeStep, renderer creation
│   ├── scene/              Scene, SceneManager, ScenePlugin, Systems
│   ├── gameobjects/        All game objects + component mixins + factories
│   ├── loader/             LoaderPlugin + filetypes/
│   ├── renderer/           webgl/ (RenderNodes, shaders) + canvas/
│   ├── physics/            arcade/ + matter-js/
│   ├── input/              keyboard/ mouse/ touch/ gamepad/
│   ├── animations/  tweens/  sound/  cameras/  tilemaps/
│   ├── math/  geom/  curves/  display/  structs/  utils/
│   ├── filters/            v4 Filter system
│   ├── data/  cache/  textures/  time/  scale/  device/  dom/  plugins/
│   └── polyfills/          requestVideoFrame (most v3 polyfills removed)
├── types/                  Generated TypeScript definitions (phaser.d.ts)
├── config/                 Webpack configs (webpack.config.js, dist, nospector)
├── scripts/                Build helpers (tsgen/, bundle-shaders.js, help.js)
├── tests/                  Vitest unit tests (mirror src/ layout)
├── changelog/              Per-version changelogs (v3/ and v4/)
├── docs/                   In-depth rendering articles (WebGL, shaders, pixel art)
├── skills/                 AI agent skill packs (one per subsystem)
├── package.json
└── README.md
```

### The `skills/` directory — a notable asset

Phaser 4 ships a comprehensive set of **AI agent skills** in `skills/` — one folder per subsystem (scenes, physics, input, animations, tilemaps, tweens, particles, cameras, filters, plus v3-to-v4-migration and v4-new-features). Each contains a `SKILL.md` with authoritative API references, idiomatic patterns, gotchas, and source-file maps. Point an AI coding agent at this folder and it gains deep, accurate knowledge of Phaser's architecture. This is a deliberate "AI-ready" design choice and is among the best in-repo agent documentation of any major framework.

### The `docs/` directory

Contains deep-dive rendering articles for Phaser 4: WebGL Compressed Textures, the Shader Guide, Pixel Art Guide, Internal Space Guide, Rendering Concepts, and the Compact Texture Atlas Format Specification. These are for engine-level understanding, not gameplay.

---

## Security Analysis

Phaser is a **client-side rendering library**, not a server or a sandbox. Its security surface is correspondingly narrow, but there are real considerations for production deployment.

### Threat model

| Concern | Assessment |
|---|---|
| Remote code execution | No `eval` of game logic; the library doesn't execute arbitrary remote code by design. The loader's `script`/`plugin` filetypes **do** inject and run external JS — treat those URLs as trusted only. |
| Asset loading & CORS | The Loader uses XHR with configurable `crossOrigin`. Cross-origin textures used in WebGL require proper CORS headers or they taint the context. |
| Data deserialization | Tilemap/atlas/animation JSON is parsed with `JSON.parse` (safe); no `eval`-based parsing. |
| Network egress | Phaser makes no telemetry calls. The only network activity is asset loading you initiate. The console banner links to `phaser.io` but makes no request. |
| Dependency surface | Exactly one runtime dependency (`eventemitter3`), minimizing supply-chain exposure. |

### Hardening recommendations

> [!IMPORTANT]
> The Loader's `this.load.script()`, `this.load.scripts()`, `this.load.plugin()`, and `this.load.scenePlugin()` methods download and **execute** JavaScript. Only ever point them at first-party, integrity-controlled URLs. Loading a script filetype from user-supplied or third-party URLs is an XSS/RCE vector.

- **Content Security Policy:** Phaser works under CSP, but WebGL shader compilation, blob URLs (default image load type is XHR→blob), and Web Audio may require `worker-src`, `blob:`, and `connect-src` allowances. Test your CSP with the actual asset pipeline.
- **CORS:** Set `loader.crossOrigin: 'anonymous'` when loading cross-origin assets destined for WebGL, and ensure the asset host returns `Access-Control-Allow-Origin`.
- **Compressed textures:** v4 expects GL-orientation (Y=0 at bottom). Re-compress assets accordingly — a mismatch is a correctness, not security, issue, but worth noting in a deployment checklist.
- **User-generated content:** If your game loads player-supplied images/levels, validate dimensions and sizes before loading to avoid memory-exhaustion DoS on the client.
- **No secrets in the bundle:** Anything in the client bundle (API keys, level data) is fully visible. Never embed secrets; gate sensitive logic behind a server.

### Input handling

Phaser calls `preventDefault` on captured keys and (configurably) mouse/touch events. The `disableContextMenu` option suppresses the right-click menu. None of this is a security boundary — it's UX. The keyboard capture list is **global across all scenes**.

---

## Performance & Operations

### Production checklist

- [ ] Use the **minified** build (`phaser.min.js` or a bundler production build).
- [ ] Set `physics.arcade.debug: false` (debug rendering is expensive).
- [ ] Use `type: Phaser.AUTO` for WebGL with Canvas fallback.
- [ ] Provide multiple audio formats (OGG + MP3) for cross-browser coverage.
- [ ] Set appropriate `scale.mode` for your target devices.
- [ ] Consider `autoMobileTextures: true` (default) for mobile batch limits.
- [ ] Remove the console banner in production if desired (`banner: false`).
- [ ] Test under your Content Security Policy.

### Batching and draw calls

WebGL performance is dominated by **draw-call count**. Batches break when:

- The texture changes (group objects by texture/atlas; use multi-atlases).
- The blend mode changes (group objects by blend mode).
- Lighting is toggled (lighting changes the shader — group lit and unlit objects).
- A filter is applied (filters force framebuffer indirection).

> [!TIP]
> For very large object counts, prefer the v4 GPU game objects: `SpriteGPULayer` (millions of sprites, one draw call) and `TilemapGPULayer` (entire layer as a single quad, per-pixel cost). Populate their buffers once — updating a static GPU buffer is expensive.

### Memory

- Textures are the dominant memory cost. Remove unused textures with `this.textures.remove(key)` and audio with `this.cache.audio.remove(key)`.
- Use object **pooling** (Groups with `maxSize` + `killAndHide`) instead of create/destroy churn for bullets, particles, enemies.
- `game.destroy(true, true)` releases internal references but precludes re-creating a Phaser instance on the page.

### Physics tuning

- `fps` (default 60) controls physics steps/sec; `fixedStep: true` decouples from render rate.
- `useTree: false` for 5,000+ dynamic bodies (RTree rebuild cost).
- `timeScale` for slow-motion/fast-forward; `overlapBias`/`tileBias` for separation tuning.

### The console banner

`DebugHeader.js` prints a styled banner with version, renderer, and audio type. It uses CSS console styling (plain text fallback on IE). Disable with `banner: false` or hide just the Phaser line with `banner: { hidePhaser: true }`.

---

## Code Quality & Conventions

### Authoring conventions

- **ES5-compatible source**, enforced by `eslint-plugin-es5`. No ES6+ syntax in `src/` — classes are built via the custom `Phaser.Class` helper (`src/utils/Class.js`).
- **JSDoc on everything.** Every class, method, property, and config option carries JSDoc that drives both the API docs and the generated TypeScript definitions. This is why source files are large.
- **One concept per file.** Each function/class typically lives in its own file; folders aggregate via an `index.js` that re-exports.
- **Component mixins over inheritance.** Game objects compose behavior from `src/gameobjects/components/*`.
- **Namespace assembly.** Entry points build the `Phaser` object from subsystem requires; `Extend` merges constants in.

### The `Phaser.Class` pattern

Because the source targets ES5, classes use a custom factory:

```js
var Config = new Class({
    initialize: function Config (config) { /* constructor */ },
    someMethod: function () { /* ... */ }
});
```

`Class.mixin` composes component objects into the prototype. (Note: a known issue, #7286, is that `Phaser.Class` including `Class.mixin` is not exposed on the public ESM namespace, which constrains third-party plugins.)

### Event-driven architecture

Nearly every subsystem extends `eventemitter3`. Managers, scenes, game objects, tweens, sounds, and cameras all emit typed events with string names and named constants (e.g. `Phaser.Scenes.Events.CREATE`). This decoupling is the backbone of cross-system and cross-scene communication.

### Build-time feature flags

Conditional compilation via Webpack `DefinePlugin`: `FEATURE_SOUND`, `WEBGL_RENDERER`, `CANVAS_RENDERER`, `WEBGL_DEBUG`. Code guarded by `if (typeof FEATURE_X)` is included or dead-code-eliminated per build target — this is how the alternate builds (`phaser-core`, `phaser-no-physics`) are produced.

---

## Testing Strategy

- **Framework:** Vitest 4 (`npm test` / `npm run test:watch`), with **jsdom** simulating the DOM.
- **Layout:** `tests/` mirrors `src/` (e.g. `tests/math/`, `tests/geom/`, `tests/physics/`, `tests/tweens/`). Coverage is strongest in the pure-logic subsystems — math, geometry, structs, actions, curves — where deterministic unit tests are tractable.
- **Type tests:** `npm run test-ts` compiles a TypeScript test project (`scripts/tsgen/test/`) against the generated `phaser.d.ts` to catch definition regressions.
- **What's hard to unit-test:** WebGL rendering output is not covered by automated tests in the standard suite — rendering correctness relies on the examples site and manual verification. This is inherent to GPU rendering and is the main coverage gap.

> [!NOTE]
> When contributing, add tests under the mirrored `tests/<subsystem>/` folder. Pure-logic changes (math, geometry, data structures) are expected to ship with unit tests; rendering changes are validated via the examples repository.

---

## Known Issues & Tech Debt

Sourced from the project's open GitHub issues at the documented commit. These reflect real, community-confirmed items as of this analysis.

### Confirmed bugs

| Issue | Summary |
|---|---|
| #7307 | `AnimationManager.toJSON()` errors when stringified indirectly |
| #7303 | Linting broken on master |
| #7289 (closed) | `filter.getPaddingCeil is not a function` |
| #7267 (closed) | `MatterTileBody` crashes on flipped tiles with collision shapes |
| #7309 | `Phaser.Math.Pow2.GetPowerOfTwo` declared in `phaser.d.ts` but undefined at runtime |
| #7305 | `GetColor32()` outputs negative values |
| #7296 | Memory leak in `TilemapLayerWebGLRenderer` |
| #7295 | `Layer` is not extended from the `GameObject` class |
| #7292 | `Blitter` doesn't destroy its Bobs |
| #7193 | Memory leak using `MobilePipeline` and `SpinePlugin` |
| #7306 | Significant FPS drop with simple `Container.addMask()` in WebGL |

### TypeScript definition debt

| Issue | Summary |
|---|---|
| #7249 | `EventEmitter` typings are unsatisfactory and unsafe |
| #7248 | Event types should be literal types instead of `string` |
| #7260 | `TweenManager.create` and similar take overly permissive `object` config types |
| #7257 | `Phaser.Math.RND.Pick` / `WeightedPick` should use `ArrayLike` |
| #7298 | Investigate alternatives to `jsdoc` for type generation |

### Architectural / API gaps

| Issue | Summary |
|---|---|
| #7286 | `Phaser.Class` (incl. `Class.mixin`) not exposed on the public ESM namespace — forces third-party plugins into inadequate mixin fallbacks |
| #7294 | No screen-reader accessibility guidance or framework-level a11y helpers |
| #7275 | "First Game" example needs upgrading for Phaser 4 |

> [!WARNING]
> Two **memory leaks** are open against the WebGL renderer paths (#7296 TilemapLayer, #7193 Spine plugin) and one against Blitter cleanup (#7292). If your game uses tilemap layers or Spine heavily and runs for long sessions, monitor memory and consider explicit teardown/recreation of affected objects.

### Accessibility

Phaser has **no built-in screen-reader support or framework-level accessibility helpers** (#7294 is an open request). Games requiring accessibility must implement it at the application layer (e.g. an off-screen DOM live region mirrored from game state).

---

## Extending Phaser

### Custom plugins

Register plugins via the config `plugins` object:

```js
const config = {
    plugins: {
        global: [
            { key: 'SaveManager', plugin: SaveManagerPlugin, start: true, data: {} }
        ],
        scene: [
            { key: 'GridPlugin', plugin: GridPlugin, mapping: 'grid' }
        ]
    }
};
```

Global plugins are accessible game-wide; scene plugins are injected into each scene under their `mapping` name. Extend `Phaser.Plugins.BasePlugin` or `ScenePlugin`.

### Custom render nodes

```js
renderer.renderNodes.addNodeConstructor('MyNode', MyNodeClass);
renderer.renderNodes.addNode('MyNode', myNodeInstance);
// Register at boot via config.render.renderNodes, then assign per object:
gameObject.setRenderNodeRole('Submitter', 'MyNode');
```

### Custom game objects

Build a class (via `Phaser.Class` or ES6 in your own project, where modern syntax is fine), mix in the components you need, and register a factory:

```js
Phaser.GameObjects.GameObjectFactory.register('myThing', function (x, y) {
    return this.displayList.add(new MyThing(this.scene, x, y));
});
// Now usable as this.add.myThing(x, y)
```

### The Extern game object

For direct WebGL access without desyncing Phaser's state wrapper, use an `Extern` game object — it resets WebGL state after rendering, making it safe to do raw `gl` calls inside it.

---

## Contributing

- **Repository:** `github.com/phaserjs/phaser` (single `master`/`main` branch).
- **License:** MIT — contributions licensed accordingly.
- **Source style:** ES5-compatible, JSDoc-annotated, one concept per file, lint clean (`npm run lint`). (Note #7303: linting is currently broken on master — verify the lint config before relying on it.)
- **Tests:** Add Vitest tests under the mirrored `tests/<subsystem>/` path for logic changes.
- **Changelog:** Each version has its own changelog under `changelog/v4/`.
- **Types:** If you change a public API, regenerate and type-check the definitions (`npm run ts`).
- **Community:** Discord (`discord.gg/phaser`), the Phaser Discourse forum, and the examples repository for rendering validation.

---

## Quick Reference

### Renderer constants (`src/const.js`)

| Constant | Value |
|---|---|
| `Phaser.VERSION` | `'4.1.0'` |
| `Phaser.AUTO` / `CANVAS` / `WEBGL` / `HEADLESS` | 0 / 1 / 2 / 3 |
| `Phaser.FOREVER` | -1 |
| `Phaser.NONE / UP / DOWN / LEFT / RIGHT` | 4 / 5 / 6 / 7 / 8 |

### Global game members (`game.*`)

`config`, `renderer`, `canvas`, `context`, `events`, `anims`, `textures`, `cache`, `registry`, `input`, `scene`, `device`, `scale`, `sound`, `loop`, `plugins`. State flags: `isBooted`, `isRunning`, `isPaused`, `hasFocus`.

### Game control

```js
game.pause();  game.resume();   // halt/continue the whole loop
game.destroy(removeCanvas, noReturn);   // async; listen for DESTROY
```

### Build variants

| Build | Excludes |
|---|---|
| `phaser.js` | nothing (full) |
| `phaser-arcade-physics.js` | Matter.js |
| `phaser-no-physics.js` | Arcade + Matter |
| `phaser-core.js` | most game objects, Geom, Tilemaps |

### Key source files

| Concern | File |
|---|---|
| Game / boot | `src/core/Game.js` |
| Config + all defaults | `src/core/Config.js` |
| Game loop | `src/core/TimeStep.js` |
| Renderer selection | `src/core/CreateRenderer.js` |
| Scene base | `src/scene/Scene.js` |
| Scene orchestration | `src/scene/SceneManager.js`, `ScenePlugin.js` |
| Injection map | `src/scene/InjectionMap.js` |
| Component list | `src/gameobjects/components/index.js` |
| WebGL renderer | `src/renderer/webgl/WebGLRenderer.js` |
| Build config | `config/webpack.config.js` |

### Documentation & resources

- API docs: https://docs.phaser.io
- Source examples: https://phaser.io/examples
- In-repo AI skills: `skills/` (one per subsystem)
- In-repo rendering deep-dives: `docs/`
- Migration & gameplay basics: the companion **[User Guide](phaserjs__phaser__UserGuide.md)**

---

_Generated June 2026 from phaserjs/phaser at 9e67ded. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
