---
mvp: no
subsystem: matrix
---

# Client Engine Feature Matrix — Phaser 3.90 vs Phaser 4.1 vs PixiJS 8.18

> **Source of truth:** `MATRIX-rows.json`. This MD is rendered from it by
> `tools/asset-catalog regen-autogen`. Do NOT hand-edit the rows / weights /
> totals tables below — edit the JSON and re-run `pnpm catalog:client`.

## Methodology

- One row per CDOC-01 feature. Each row carries `bnoUsage` (`heavy` / `light` /
  `none`), a `weight` 1..5, and a per-engine `grade` cell (`native` / `plugin` /
  `manual` / `hard`) per PITFALLS D3.
- Grade-to-score mapping: `native`=4, `plugin`=3, `manual`=2, `hard`=1.
- Weighted score per engine = Σ(row.weight × score(row, engine)).
- D-13 hard-knockout: any row with weight ≥ 4 graded `hard` for Phaser 3
  flips the ADR to PixiJS (or forces explicit justification of the workaround).

## Engine Versions

- **Phaser 3.90.0** ("Tsugumi") — npm pinned via `phaser@3.90.0`. STACK.md
  preferred MVP engine.
- **Phaser 4.1.0** ("Salusa") — npm `latest` as of 2026-05-02. STACK.md "still
  settling" caveat; v4 is GA but ecosystem (plugins, tutorials, AI scaffolding)
  is less mature than v3.
- **PixiJS 8.18.1** — STACK.md fallback. Pairs with `@pixi/tilemap@5.0.2` +
  `howler@2.2.4` if chosen (Pixi has no first-class scene/physics/audio).

## Weight Distribution

<!-- AUTOGEN:matrix-weights:start -->
| Subsystem | Sum of weights |
|-----------|----------------|
| animation | 8 |
| audio | 1 |
| client-networking | 2 |
| collision | 11 |
| input | 11 |
| rendering | 22 |
| save-load | 1 |
| scene-room-model | 12 |
| ui-and-menus | 4 |
| **Total** | **72** |
<!-- AUTOGEN:matrix-weights:end -->

## Rows

<!-- AUTOGEN:matrix-rows:start -->
| Row ID | Subsystem | Feature | Weight | Phaser 3.90 | Phaser 4.1 | PixiJS 8.18 | Cite |
|--------|-----------|---------|--------|-------------|------------|-------------|------|
| MX-ANIM-01 | animation | Animated sprite frames with per-frame `image_speed` interpolation | 4 | native (4) | native (4) | native (4) | [animation.md#image-speed-and-frames](animation.md#image-speed-and-frames) |
| MX-ANIM-02 | animation | Sprite-swap animation (`sprite_index = X` mid-flight) — Hexport state machine on player | 4 | native (4) | native (4) | native (4) | [animation.md#sprite-swap](animation.md#sprite-swap) |
| MX-AUDIO-01 | audio | WAV / MIDI / MP3 playback (NO embedded audio in client-5-8 per Plan 02-05 finding) | 1 | native (4) | native (4) | plugin (3) | [audio.md#no-embedded-audio](audio.md#no-embedded-audio) |
| MX-COLLIDE-01 | collision | Per-object Collision events with target object filter (movement vs walls) | 5 | native (4) | native (4) | manual (2) | [collision.md#per-object-collision-events](collision.md#per-object-collision-events) |
| MX-COLLIDE-02 | collision | `collision_rectangle` / `collision_line` / `place_meeting` runtime queries | 4 | native (4) | native (4) | manual (2) | [collision.md#runtime-queries](collision.md#runtime-queries) |
| MX-COLLIDE-03 | collision | Precise per-pixel collision masks (sprite `precise: true`) | 2 | plugin (3) | plugin (3) | manual (2) | [collision.md#precise-masks](collision.md#precise-masks) |
| MX-INPUT-01 | input | Per-key keyboard event handling (GM Keyboard-N events on object 0042-player) | 5 | native (4) | native (4) | manual (2) | [input.md#keyboard-events](input.md#keyboard-events) |
| MX-INPUT-02 | input | Key-press vs key-held vs key-release distinction (GM KeyPress-N / Keyboard-N / KeyRelease-N) | 4 | native (4) | native (4) | manual (2) | [input.md#key-pressed-vs-held-vs-released](input.md#key-pressed-vs-held-vs-released) |
| MX-INPUT-03 | input | Mouse input (verify usage; some menus may rely on click) | 2 | native (4) | native (4) | native (4) | [input.md#mouse](input.md#mouse) |
| MX-NET-01 | client-networking | WebSocket client integration (Phase 6 uses Colyseus client; engine-agnostic) | 2 | native (4) | native (4) | native (4) | [client-networking.md#39dll-wrapper](client-networking.md#39dll-wrapper) |
| MX-RENDER-01 | rendering | Sprite rendering with origin point + bounding box + collision mask (GM `sprite_index`/`mask_index`/`originX`/`originY`/`bbox*`) | 5 | native (4) | native (4) | manual (2) | [rendering.md#sprite-rendering](rendering.md#sprite-rendering) |
| MX-RENDER-02 | rendering | Tile-background drawing with offset/separation (GM background-as-tileset; rooms use tiles) | 4 | native (4) | native (4) | plugin (3) | [rendering.md#tile-backgrounds](rendering.md#tile-backgrounds) |
| MX-RENDER-03 | rendering | Per-instance integer depth ordering (GM `depth` int32; lower draws on top) | 4 | native (4) | native (4) | manual (2) | [rendering.md#depth-ordering](rendering.md#depth-ordering) |
| MX-RENDER-04 | rendering | Custom-font text rendering (GM `draw_set_font` + `draw_text`; resolved action 523/525 — OCRA + Fixedsys) | 4 | native (4) | native (4) | native (4) | [rendering.md#draw-set-font](rendering.md#draw-set-font) |
| MX-RENDER-05 | rendering | Color tinting + blend modes (GM `draw_sprite_ext` blend / alpha args) | 3 | native (4) | native (4) | native (4) | [rendering.md#draw-sprite-ext](rendering.md#draw-sprite-ext) |
| MX-RENDER-06 | rendering | Surface / render-texture (GM `surface_create`/`surface_set_target`) | 2 | native (4) | native (4) | native (4) | [rendering.md#surfaces](rendering.md#surfaces) |
| MX-SAVE-01 | save-load | Client-side state save/load (Settings.bno via file_text_*; server saves are Phase 3) | 1 | native (4) | native (4) | native (4) | [save-load.md#client-side-save](save-load.md#client-side-save) |
| MX-SCENE-01 | scene-room-model | Room model with size, view, view-port, view-following (16 rooms catalogued) | 5 | native (4) | native (4) | manual (2) | [scene-room-model.md#room-model](scene-room-model.md#room-model) |
| MX-SCENE-02 | scene-room-model | Room transitions (`room_goto` / `room_restart` / `room_next` / `room_previous`) | 4 | native (4) | native (4) | manual (2) | [scene-room-model.md#room-transitions](scene-room-model.md#room-transitions) |
| MX-SCENE-03 | scene-room-model | Per-room creation code + per-instance creation code | 3 | native (4) | native (4) | manual (2) | [scene-room-model.md#creation-code](scene-room-model.md#creation-code) |
| MX-UI-01 | ui-and-menus | DOM-overlay UI vs canvas UI compatibility (chat HUD, Main_Menu, Online_Lobby, Settings_Menu, Online_Command_Screen) | 4 | native (4) | native (4) | native (4) | [ui-and-menus.md#dom-overlay-vs-canvas](ui-and-menus.md#dom-overlay-vs-canvas) |
<!-- AUTOGEN:matrix-rows:end -->

## Weighted Totals

<!-- AUTOGEN:matrix-totals:start -->
| Engine | Weighted total |
|--------|----------------|
| phaser-3.90 ✓ | 286 |
| phaser-4.1 ✓ | 286 |
| pixi-8.18 | 201 |
<!-- AUTOGEN:matrix-totals:end -->

## Hard-Knockout Watch

Per D-13: any row with weight ≥ 4 graded `hard` for Phaser 3 flips the ADR to
PixiJS (or requires explicit justification of the workaround in the ADR
Decision section). Per Plan 02-06 audit at end-of-Phase-2: zero such rows
exist in this MATRIX — Phaser 3 grades range from `native` (most rows) to
`plugin` (precise per-pixel collision masks; non-decisive at weight 2).

If a future row is added with weight ≥ 4 graded `hard` for Phaser 3, the ADR
Decision section MUST justify either (a) flipping to PixiJS or (b) accepting
the workaround cost with explicit reasoning.

See [adr/0001-client-engine.md](../adr/0001-client-engine.md) for the locked
decision.
