> ## Documentation Index
> Fetch the complete documentation index at: https://docs.profy.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Game Studio

> profy-game-studio — browser 2D/3D game development with an engine selection matrix, an assertable debug projection, and automated playtesting

# Game Studio (profy-game-studio)

## One-line definition

`profy-game-studio` lets an expert build a **genuinely playable browser game** from nothing — pick an engine, lay out the architecture, write the game loop, wire up physics, build the HUD — and then **playtest it repeatedly until it actually plays** before handing it over. That last step is the fundamental difference between this plugin and "ask an AI to write a small game".

## Activation

| Field             | Value                                              |
| ----------------- | -------------------------------------------------- |
| Plugin ID         | `profy-game-studio`                                |
| `user_selectable` | `true` — **tick it manually in the plugin panel**  |
| `conditions`      | `{ "sandbox_mode": "!none" }` (requires a sandbox) |
| Tools injected    | `browser` (only)                                   |
| Prompt injected   | `GAME_STUDIO.md`                                   |
| Declared skills   | 9 (10 skill directories on disk)                   |
| References        | 16 files under `references/`, read on demand       |

<Note>
  This plugin **injects no proprietary tools** — it binds only `browser`. The reasoning is direct: game development uses ordinary sandbox capabilities (write files, run a dev server, install dependencies). The one thing missing is "see the screen and operate it", which is precisely the browser tool. So ticking Game Studio always lights up browser too; without it the playtest loop is empty.
</Note>

## Engine selection matrix

Engine choice is the first decision in a game project and the most expensive one to get wrong — a bad pick means a rewrite. The prompt carries a full decision table:

| Game type              | Engine                 | Why                                  |
| ---------------------- | ---------------------- | ------------------------------------ |
| 3D first/third person  | Three.js (vanilla)     | Full control, best performance       |
| 3D with React UI       | React Three Fiber      | Declarative, component model         |
| 2D platformer/arcade   | Phaser 3               | Batteries included, physics built in |
| 2D with React UI       | Phaser + React overlay | Best of both worlds                  |
| Simple 2D (no physics) | Canvas2D API           | Zero dependencies                    |

### Dimension-by-dimension

| Dimension          | Three.js          | R3F                          | Phaser              | Canvas2D  |
| ------------------ | ----------------- | ---------------------------- | ------------------- | --------- |
| 3D support         | Full              | Full                         | None                | None      |
| 2D support         | Possible, awkward | Possible, awkward            | Full                | Basic     |
| Built-in physics   | No (add Rapier)   | No (add @react-three/rapier) | Yes (Arcade/Matter) | No        |
| React integration  | Manual            | Native                       | Overlay             | Overlay   |
| Learning curve     | Medium            | Medium-high                  | Low-medium          | Low       |
| Bundle size        | \~150KB           | \~200KB                      | \~300KB             | 0         |
| Mobile performance | Good              | Good                         | Excellent           | Excellent |

### When *not* to use each

This inverse list is more useful than the recommendations:

* **Three.js**: not for simple 2D games — massive overkill
* **R3F**: not when entity counts are high; React overhead becomes the bottleneck
* **Phaser**: not for 3D, and not when you need a custom render pipeline
* **Canvas2D**: not for complex physics or 3D

If you have an engine preference, **say so in the request**. Otherwise the model decides from the table above — usually correctly, but not necessarily the way you wanted.

## Six architecture principles

The prompt hard-codes these six, so the structure of the code you receive is predictable:

<AccordionGroup>
  <Accordion title="1. Game loop: fixed-timestep physics (60Hz), variable render">
    Logic must never be tied to frame rate. When it is, the character sprints on a high-refresh display, crawls on a weak machine, and physics tunnels through geometry whenever frames drop. This is the most common structural mistake in first games and the hardest to fix later.
  </Accordion>

  <Accordion title="2. ECS-lite: separate data from logic">
    Even without a formal ECS framework, keep state and rendering apart. The benefit only shows up when requirements change — if "add a new enemy type" requires touching render code, this principle was not followed.
  </Accordion>

  <Accordion title="3. Input abstraction: raw events map to semantic actions">
    Map to `jump` and `move_left` rather than checking `event.key === 'ArrowLeft'` in a dozen places. Support keyboard, touch, and gamepad. This turns "add gamepad support" from a rewrite into one more mapping layer.
  </Accordion>

  <Accordion title="4. Asset pipeline: async preload, progress, aggressive caching">
    Load assets before the game starts and show progress. Loading while playing is not smoothness, it is stutter.
  </Accordion>

  <Accordion title="5. State machine: menu / playing / paused / game-over as an explicit FSM">
    Game states belong in an explicit finite state machine, not scattered booleans. A condition like `isPaused && !isGameOver && hasStarted` is the symptom of a missing state machine.
  </Accordion>

  <Accordion title="6. Test hooks: interactive controls carry data-game-action">
    Give interactive controls stable `data-game-action` attributes so browser automation can replay user paths semantically instead of clicking brittle visual coordinates. This principle exists to serve the automated playtest below.
  </Accordion>
</AccordionGroup>

## The debug projection: making playtests assertable

This is the technical core of the whole workflow. Every game must expose a **read-only debug projection**:

```js theme={null}
window.__GAME_DEBUG__ = {
  snapshot: () => ({ phase, score, health, player, entityCount })
};
```

The load-bearing words are "projection" and "read-only" — it does not duplicate game state, it reads the current state out. So it introduces no new state-desync failure mode of its own.

With it, playtesting shifts from "look at a screenshot and guess" to "read state and assert":

```js theme={null}
browser(action="evaluate", code="window.__GAME_DEBUG__?.snapshot()")
```

The model can confirm directly that `player.x` really increased after a right-arrow press, and that `score` really incremented after collecting a coin — rather than comparing two screenshots for pixel movement. When a specific mechanic needs proving, the projection is extended on demand (a tower defense game gains `waveIndex` and `towerCount`).

<Info>
  Screenshots and state assertions solve **different** problems. A screenshot catches "nothing rendered". A state assertion catches "it rendered correctly but the logic is wrong". Doing only the first ships a beautiful game where the keys do nothing — precisely the blind spot of purely visual verification.
</Info>

## The automated playtest loop

The "Vision in the Loop" protocol, in its game-specific form, adds interaction replay:

<Steps>
  <Step title="Generate or modify code">
    Write the game code.
  </Step>

  <Step title="Capture">
    `browser(action="screenshot", url="localhost:5173")`
  </Step>

  <Step title="Read pixels">
    `image(action="read", ..., prompt="Check gameplay visibility, HUD, framing, artifacts, and blank regions")`
  </Step>

  <Step title="Assert state">
    `browser(action="evaluate", code="window.__GAME_DEBUG__?.snapshot()")`
  </Step>

  <Step title="Replay interaction">
    Start control: `document.querySelector('[data-game-action=start]')?.click()`

    Keyboard: `dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))`
  </Step>

  <Step title="Diagnose jointly">
    Read the screenshot and the state together against the symptom table below.
  </Step>

  <Step title="Loop on any issue">
    **No iteration limit** — loop until it is visually correct and playable.
  </Step>
</Steps>

Symptom-to-cause table, built into the prompt:

| Symptom                  | Direction                                                |
| ------------------------ | -------------------------------------------------------- |
| Black or blank screen    | WebGL context error or script crash → check console      |
| Sprites / models missing | Asset load failure or wrong path                         |
| Wrong layout             | Camera position / canvas size / CSS                      |
| Physics broken           | Timestep or collision setup → debug via state inspection |
| Sluggish                 | Too many draw calls or GC pressure                       |

The prompt's closing instruction is blunt: **do not ask "does this look right?" about something you can verify yourself.** The user hired the expert to produce a working game, not to QA its work.

## Game-specific acceptance

Beyond general visual checks, games must pass:

* The player responds to input (inject test input → screenshot after N frames)
* Score and UI update correctly
* Objects do not fall through floors (physics tunneling)
* The game-over condition triggers appropriately
* Restart returns to a clean state with no leaks

### Performance gates

| Metric     | Mobile   | Desktop  |
| ---------- | -------- | -------- |
| FPS        | ≥ 30     | ≥ 60     |
| Draw calls | \< 200   | \< 500   |
| Load time  | \< 5s    | \< 3s    |
| Memory     | \< 256MB | \< 512MB |
| Bundle     | \< 5MB   | \< 20MB  |

### Edge cases

The canvas must adapt on window resize, and the game must pause on tab switch (`visibilitychange`). Both are routinely forgotten; the symptom of the second is coming back to a tab to find your character already dead.

## Skills and references

### 9 declared skills (text injected into the prompt)

| Skill                    | Covers                          |
| ------------------------ | ------------------------------- |
| `game-studio`            | Overall doctrine                |
| `three-webgl-game`       | Vanilla Three.js 3D games       |
| `react-three-fiber-game` | Declarative 3D with R3F         |
| `phaser-2d-game`         | Phaser 3 2D games               |
| `web-3d-asset-pipeline`  | 3D asset pipeline               |
| `web-game-foundations`   | Loop, input, state fundamentals |
| `game-ui-frontend`       | HUD and menus                   |
| `game-playtest`          | Playtest verification           |
| `sprite-pipeline`        | Sprite sheet pipeline           |

A tenth directory, `physics-game`, is undeclared in the manifest but loadable by name through the `skill` tool.

### 16 reference files (read on demand, no default context cost)

`alternative-3d-engines` / `engine-selection` / `frontend-prompts` / `gltf-loading-starter` / `phaser-architecture` / `playtest-checklist` / `rapier-integration-starter` / `react-three-fiber-stack` / `react-three-fiber-starter` / `sprite-pipeline` / `three-hud-layout-patterns` / `three-webgl-architecture` / `threejs-stack` / `threejs-vanilla-starter` / `web-3d-asset-pipeline` / `webgl-debugging-and-performance`

The four `-starter` files are copy-ready templates.

## Executable examples

<AccordionGroup>
  <Accordion title="2D platformer">
    ```
    Build a side-scrolling platformer: a character that moves left/right and jumps,
    a few platforms, falling off restarts. Collecting coins adds score, shown top-right.
    Touch controls should work on mobile.
    ```

    Selects Phaser 3 (2D plus physics), adds `data-game-action` hooks, and verifies during playtest that jumps genuinely rise and fall and that falling genuinely restarts.
  </Accordion>

  <Accordion title="3D first-person exploration">
    ```
    Build a first-person walkthrough: WASD movement, mouse look, a few exhibits you
    can walk up to, with descriptive text appearing when you get close.
    ```

    Selects vanilla Three.js; the work concentrates on camera control and collision.
  </Accordion>

  <Accordion title="Tower defense with a React UI">
    ```
    Build a tower defense: 3D battlefield with a React panel for building towers and
    showing wave info. Three tower types, enemies in waves, you lose at zero health.
    ```

    Classic R3F territory. The debug projection gains fields like `waveIndex` and `towerCount` so wave logic can be asserted.
  </Accordion>

  <Accordion title="Fixing an existing game">
    ```
    My game is at <path>. Enemies pass through walls. Find and fix it, then run a
    playtest to confirm collision works.
    ```

    Tunneling is usually a fixed-timestep violation (principle 1) or a collider size mismatch.
  </Accordion>
</AccordionGroup>

## Boundaries and failure modes

| Symptom                                            | Cause                                           | Fix                                                                     |
| -------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------- |
| Black screen                                       | WebGL context error or script crash             | Have the model read console output instead of tweaking visuals          |
| Renders fine but keys do nothing                   | Visual verification only, no state assertion    | Ask it to "verify input takes effect using `__GAME_DEBUG__.snapshot()`" |
| Character falls through the floor                  | Physics tunneling — timestep or collider        | Explicitly require fixed 60Hz timestep                                  |
| Fast on a high-refresh display, slow on a weak one | Logic tied to frame rate (violates principle 1) | Require fixed timestep with variable render                             |
| Unplayably slow on mobile                          | Exceeds the mobile performance gates            | Walk the table above item by item                                       |
| Come back to the tab and you're dead               | `visibilitychange` unhandled                    | Ask for pause-on-hidden                                                 |
| The model asks "does this look right?"             | Self-check protocol was not completed           | Remind it to screenshot and assert state itself                         |
| Ticked but inactive                                | `sandbox_mode` is `none`                        | Requires a sandbox                                                      |

### Known defect: three sprite scripts are placeholders

The three scripts under `scripts/` currently **only print a line of text** — there is no real implementation:

| Script                           | Actual content                                                          |
| -------------------------------- | ----------------------------------------------------------------------- |
| `normalize_sprite_strip.py`      | `print("normalize_sprite_strip: normalizes frame sizes")`               |
| `build_sprite_edit_canvas.py`    | `print("build_sprite_edit_canvas: generates blank sprite template")`    |
| `render_sprite_preview_sheet.py` | `print("render_sprite_preview_sheet: generates preview contact sheet")` |

The symptom: calling them does not error. They "succeed" and return a line of text, but the sprite output you wanted never appears.

**Workaround**: have the model process sprite sheets directly with Pillow (available in the sandbox), or follow the `sprite-pipeline` skill document manually. **How to spot it**: the model reports "sprite preview sheet generated" but no image file exists.

## Verify

<Steps>
  <Step title="Confirm activation">
    Ask "can you build browser games? Which engine would you use?" When active, the expert produces the selection matrix and asks about your game type; when inactive it just offers to write code.
  </Step>

  <Step title="Minimal playable">
    "Make the simplest thing: a square that moves with arrow keys and stops at the edges." You should get something immediately playable, not code for you to run.
  </Step>

  <Step title="Confirm the debug projection exists">
    In the game page console, run `window.__GAME_DEBUG__.snapshot()`. A state object means the architecture rules were followed; `undefined` means this step was skipped and playtesting has degraded to purely visual.
  </Step>

  <Step title="Confirm the self-check loop ran">
    Look for screenshot and `evaluate` calls before delivery. Handing you raw code instead usually means the browser tool was not bound.
  </Step>
</Steps>

## Related pages

<CardGroup cols={2}>
  <Card title="3D development" icon="box" href="/en/documentation/plugins/3d-development">
    Blender / Godot bridges and the 3D asset pipeline
  </Card>

  <Card title="Visualize" icon="bar-chart-2" href="/en/documentation/plugins/visualize">
    The `inspect_3d` Three.js scene diagnostic tool
  </Card>

  <Card title="Browser automation" icon="globe" href="/en/documentation/capabilities/browser-automation">
    The browser capability the playtest loop depends on
  </Card>

  <Card title="Sites" icon="rocket" href="/en/documentation/capabilities/sites">
    Publishing the finished game as a reachable site
  </Card>
</CardGroup>

<Note>
  Verified 2026-08-11. Sources: `services/agent-runtime/src/plugins/builtin/game-studio/plugin.json`, `prompts/GAME_STUDIO.md`, `references/{engine-selection,playtest-checklist}.md`, `scripts/*.py`.
</Note>
