# Game Maker A browser-based tool "one step before a game engine": create **assets** (scenes, cameras, sprites), place them into scenes, tweak their properties in an inspector and press **Play** – all in one program, all drawn inside a single WebGL canvas. Live: · Widget playground: ## Concepts | Term | Meaning | |---|---| | **Asset** | Named thing with a type (`scene`, `camera`, `sprite`) and typed properties. Assets have **no position**. | | **Scene object** | An asset placed into a scene: a link to the asset + a **position** (`x`, `y`, `z`) + sparse **overrides** of the asset's properties. Scenes cannot be placed. | | **Override** | A per-object value that replaces the asset's value for one property. Only changed keys are stored; the inspector marks them bold and offers a reset (↺). | | **Default** | Every property has a schema default. Asset values that differ from it are marked bold in the inspector and can be reset (↺) the same way an override can. | | **Edit mode** | The viewport draws the project model; panels, grid, gizmos and drag-to-move are active. | | **Play mode** | A `World` is instantiated from the active scene (one entity per scene object, resolved properties as mutable state). Systems tick it; nothing writes back. **Stop discards the world.** | **The camera defines what is rendered.** A camera asset has a view size (`width` × `height`, default 640×360) and a `zoom`; the frame it covers in world units is `size / zoom`. In edit mode that frame is drawn as an outline. In play mode only that frame is rendered – fitted into the viewport with black letterboxing – and objects outside it are culled. A scene without a camera renders nothing in play mode. Play has no built-in behaviour (no input, no movement): behaviour will come with scripting in a later phase. **Editor symbols are always on top.** The viewport draws scene content (sprites) in an `objects` layer and editor symbols (camera icon, labels) in an `overlay` layer above it, so a sprite can never hide a symbol regardless of z or hierarchy order. **Z-order is part of the position.** `position.z` is the draw order of a scene object: higher `z` is drawn on top; objects with equal `z` keep their hierarchy order. It is edited in the inspector next to X/Y, undoable like any move, and applies in edit and play mode alike. ## Stack - **PixiJS v8** – rendering and pointer events. No game engine; runtime systems live in `src/runtime/`. - **Own canvas widget toolkit** (`src/ui/`) – panels, buttons, lists, text/number/color fields, popups. The only DOM element is a hidden `` used as a keyboard bridge (IME, mobile keyboard, clipboard) while a canvas text field is focused. - **Vite + TypeScript**, **Vitest** for the pure core. - **PHP 8** JSON API (`api/`) storing one JSON file per project in `data/projects/`, with atomic writes and an integer revision that rejects stale saves (HTTP 409). ## Layout ``` web/index.html Vite source page (dev) index.html built page (generated, served) src/core/ model, property schema, resolve, commands, store – no Pixi imports src/runtime/ World, Entity, System interface, Input tracker (no systems yet) src/ui/ widget toolkit (generic, knows nothing about assets) src/viewport/ scene renderer: model in edit mode, world in play mode src/editor/ App composition, state, panels, inspector field factory api/ PHP backend data/projects/ persisted projects tests/ Vitest unit tests tools/ e2e.mjs (CDP smoke test), seed-demo.mjs ``` Rules that keep it organised: `core/` imports nothing from other layers; `ui/` never sees assets; position exists only on scene objects; overrides are merged in exactly one place (`core/properties/resolve.ts`); the inspector is generated from `PropertySchema` – a new property is one schema line. ## Development Node 22 is required (on this machine: `PATH=/opt/node22/bin:$PATH`). ```bash npm install npm run dev # http://localhost:5175/game-maker/dist/ – API calls are proxied to production npm test # unit tests (core) npm run typecheck npm run build # writes dist/ and regenerates index.html node tools/seed-demo.mjs # reset the demo project to the state the e2e run expects node tools/e2e.mjs # headless end-to-end run with screenshots in /tmp/gm-e2e-*.png ``` The workspace folder itself is what nginx serves, so `npm run build` is the deployment. ## API All routes go through `api/index.php?r=…` (the site's nginx does not pass `PATH_INFO`). | Method | Route | Body → Result | |---|---|---| | GET | `projects` | `[{id,name,revision,updatedAt}]` | | POST | `projects` | `{name}` → project document (201) | | GET | `projects/{id}` | `{id,name,revision,updatedAt,project}` | | PUT | `projects/{id}` | `{revision, project}` → `{revision,updatedAt}` or **409** `{revision}` when stale | | DELETE | `projects/{id}` | 204 | There is no authentication yet; `data/` is world-readable. Both are phase-2 topics. ## Keyboard `Ctrl+Z` / `Ctrl+Shift+Z` / `Ctrl+Y` undo/redo · `Delete` remove selection · `Ctrl+Enter` play · `Esc` stop · `Tab` cycles fields · number fields: drag to scrub (`Shift` ×10, `Alt` ×0.1), `↑`/`↓` step · viewport: wheel zooms, middle-drag or `Shift`+drag pans. ## Out of scope for phase 1 Textures on sprites, physics, scripting, multi-select, z-order UI, auth, editing while playing, touch-optimised layout.