The editor gets a name of its own that fits beside Brandmauer,
Glutspeicher and Feuerwehr. This is stage 1, 2 and 4 of #422: the
name, the URL and the stored state in the browser. Repository, CI
runners, containers and the GAME_MAKER_* variables follow in stage 3.
- All paths come from SITE_PATH in src/core/site.ts: the API endpoint,
the data folders, the play page and the base in vite.config.ts. A
later move is one line instead of fifteen.
- The window title, the sign-in screen, the playground and the README
say Feuerwerk. The package, __feuerwerkPlay, the take worklet and the
writing app in Ogg and Matroska follow.
- The session cookie is now feuerwerk_session, so everyone signs in
once more.
- localStorage keys are now feuerwerk.*. moveOldStoredKeys copies the
old game-maker.* keys over on the first start, so settings, panel
sizes and save games survive.
- tools/ci/serve.sh serves the build under /feuerwerk/. The variable
GAME_MAKER_BASE_URL keeps its name until stage 3.
Outside the repository, nockal.com/feuerwerk/ is a second symlink to
the same tree. nginx sends the bare /game-maker/ folder there with a
301. Everything else under /game-maker/ is still served directly, so
shared play links and the API keep working.
Finding: when nginx redirects a path without a trailing slash or with
a query, the Brandmauer answers with its own 307 to the same URL plus
a slash, placed after the query. A play link would lose its token that
way, which is why only the bare folder is redirected.
Tests:
- Gate on melektaus-atx: CHECK EXIT 0, 307 files, 4756 tests
(4 new in site.test.ts).
- Counter-check: when the migration misses the old prefix, 3 of the
4 new tests fail.
- e2e 60 under /feuerwerk/: ALL PASSED, 20 checks.
- From outside: /feuerwerk/ 200, /game-maker/ 301, an old play
link 200.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A project could only be played inside the editor, behind the login.
To show a game to someone else there was no way short of an account.
A release freezes the saved project under a random token and serves
it on a page that holds nothing but the game view.
- Menu -> Release... opens a dialog with the state of the release,
the link with Copy link and Open, Publish or Update release, and
Take down behind a confirmation.
- Publish saves first and refuses while anything is unsaved, like
snapshots. Updating keeps the link. Take down kills it, and a new
Publish hands out a new token.
- The link is play.html?game=<32 hex>. The page needs no session,
paints Theme.colors.background, zooms with Auto and waits for
"Click to play" so that sound and keyboard focus are allowed.
- The game always starts in the start scene. Persistent variables
are stored per link in the browser. Plugins are not loaded, and the
dialog warns about them and about a project without a scene.
- GET ?r=releases/{token} is the only route besides auth/* that works
without a login. The public answer carries the token as project.id,
so the project id stays hidden. Release files are written with 0640
and nginx does not serve them directly (measured 403).
- Starting, the frame and Load scene moved into runtime/GameRun.ts.
Play in the editor and the play page run through the same code.
- WorldViewport moved to src/viewport/ and no longer needs the
EditorState. Pointer forwarding (gamePointer.ts) and setting up
textures, fonts and sounds (projectFiles.ts) exist only once.
- Vite builds two pages. The play page loads 4 kB of its own code
plus the shared chunk (Pixi, runtime, drawing, 653 kB) and Rapier,
without the editor.
Found on the way, not fixed here: data/projects/<id>.json answers 200
without a login because the files are 0644.
Tests:
- ./check.sh: 194 files, 2808 tests, exit 0.
- tests/php/releases.test.php: 20 checks, also added to CI.
- New e2e module 60 with 10 checks, played in a browser without a
session. Module 46 counts the menu entries and was updated.
- Full e2e run: 1572 checks passed.
- Counter-checks: 3 unit, 2 PHP and 3 browser mutations all fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The workflow ran everything in one job: check.sh, then build, then the e2e suite and the smokes.
The runner has two job slots and four cores, so the second slot lay idle for the whole run, and a
lint error was reported only after the full suite had passed in front of it - six minutes instead
of one.
Now `gate` and `suite` are two jobs without `needs:`. They start together, both have to pass.
The price is contention: while the gate runs, vitest and chromium share four cores, and the suite
measures text widths and frame counts, which a loaded machine reports differently. The gate
therefore caps vitest at two workers via GM_TEST_WORKERS, so two cores stay free for the browser
over the whole overlap. Locally the variable is unset and nothing changes.
Measured before the push: the cap costs vitest 4.4s (16.6s -> 21.0s), check.sh exits 0 with it,
build ok. Whether the split is worth it is a question for the runner - the expected net gain is
about 50s, and the acceptance is a measurement over at least two runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Canvas-only 2D game editor/runtime built on PixiJS v8 + Vite + TypeScript.
- core: asset model (Scene/Camera/Sprite), scene objects with sparse
overrides, property schema + resolve, command stack (undo/redo),
serialization and project store interface (16 unit tests)
- ui: custom in-canvas widget toolkit (panels, fields, color picker,
list/scroll, popup) with a hidden <input> keyboard bridge
- editor: asset/hierarchy panels, schema-driven inspector, toolbar
- viewport: box rendering, camera frame, selection gizmo, drag/zoom/pan
- runtime: world/entity instantiation for play mode, input, camera follow
- api: PHP JSON project store with revision counter (409 on conflict)
- tools: CDP-based e2e smoke test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>