Files
feuerwerk/README.md
T
rootandClaude Fable 5.1 aaa8154adf Camera view frame, viewport clipping, overlay layer for editor symbols
- Camera assets get width/height properties; play mode renders only the
  camera frame (letterboxed, masked, culled) and nothing without a camera
- Remove demo arrow-key/camera-follow systems from play mode
- Clip world and gizmos to the viewport (selection frame no longer leaks
  over the left panel)
- Blur focused widget on pointerdown outside it so inspector edits commit
  before selection changes
- Separate objects/overlay layers so camera symbols always render on top
- Add tools/seed-demo.mjs for reproducible E2E runs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 11:13:08 +02:00

5.1 KiB
Raw Blame History

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: https://nockal.com/game-maker/ · Widget playground: https://nockal.com/game-maker/?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 + 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 (↺).
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 hierarchy order. Z-order between sprites is not configurable yet.

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 <input> 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).

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 <projectId>   # reset the demo project to the state the e2e run expects
node tools/e2e.mjs <projectId>         # 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.