diff --git a/API.md b/API.md index 4a38020..4b99a98 100644 --- a/API.md +++ b/API.md @@ -20,13 +20,14 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT | GET | `sounds` | `[{file,name,bytes,updatedAt}]` — what the sound picker offers | | POST | `sounds&name=jump.wav` | **raw sound bytes** → `{file,name,bytes}` (201) | | DELETE | `sounds/{file}` | 204 | +| POST | `pixels` | **raw cel bytes** → `{file,bytes}` (201) — the pixels of one layer on one frame of [pixel art](Pixel-Art); **422** for anything that is not a cel. No listing and no delete: a cel is found by the name in the project | | GET | `plugins` | `[{file,bytes,updatedAt}]` — every `.js` in `data/plugins/`, sorted by file name | | GET | `plugins/{file}` | the module source as `text/javascript` (`nosniff`, `no-cache`); 404 for anything but a bare file name there — see [Plugins](Plugins) | **Everything but `auth/*` needs a session** and answers **401** `{"error":"Sign in first"}` without one. Projects belong to an account: `projects` lists only the caller's own, and somebody else's project answers **404**, not 403 — that an id exists is already more than a stranger should learn. -Images, fonts, sounds and plugins are shared between accounts; being signed in is still the price +Images, fonts, sounds, cels and plugins are shared between accounts; being signed in is still the price of admission. See [Accounts](Accounts). Which editor plugins a project switches on is the project's own field `plugins` (#119); the server @@ -46,7 +47,7 @@ own caps behind nginx's: 20 MB per request, 4 MB per image or font and 16 MB per a single music track is worth more than four typefaces. **nginx's own limit is the one that actually bites** (1 MB today, see `MAX_UPLOAD_BYTES` in `textureImport.ts`): raising the API's cap without raising that one changes nothing. The stored files are served statically from -`data/textures/`, `data/fonts/` and `data/sounds/`, so reading one never runs PHP. An image is +`data/textures/`, `data/fonts/`, `data/sounds/` and `data/pixels/`, so reading one never runs PHP. An image is downscaled and re-encoded in the browser when it would not fit; a font or a sound cannot be, so an oversized one is refused rather than shrunk. @@ -55,6 +56,14 @@ The format of a font or a sound is read from the file's **own signature**, never nothing at all for the same `.m4a`). MP3 is the one with no single signature — either an `ID3` tag or a frame header of eleven set bits, which is what `SoundStore::mp3Extension` demands. +**A cel** (#130) is `GMPX`, a version byte (1), a reserved byte, width and height as little-endian +16-bit numbers, then one palette index per pixel, row by row; 0 is transparent, a side is 1 to 512 +pixels. It is stored as `data/pixels/{sha256}.px`. The editor works the same name out in the browser +before it uploads, so a stroke can name its cel in the project at once; the name the server answers +with is the check that both hashed the same bytes. A pixel art asset in the project document holds +`width`, `height`, `layers` (`{id,name,opacity,hidden?}`) and `frames` (`{id,cels}`, `cels` maps a +layer id to a cel file), a palette asset `colors` (0xRRGGBB, index 1 first). + **`name` is the original file name**, kept beside the stored file as `{file}.json` and returned with it. The stored file is always `{sha256}.{ext}`, so the name is a caption and never a path: the server strips directories, control characters and anything not valid UTF-8, and caps it at diff --git a/Architecture.md b/Architecture.md index 40ce977..7f79c79 100644 --- a/Architecture.md +++ b/Architecture.md @@ -72,10 +72,10 @@ ``` web/index.html Vite source page (dev) index.html built page (generated, served) -src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:@` and the register of readers that tell the picture path an asset's canvas size and revision), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), graphLookup (what a graph sees of the project: behaviors to run, state machines to read), Scope (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by) +src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:@` and the register of readers that tell the picture path an asset's canvas size and revision), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), graphLookup (what a graph sees of the project: behaviors to run, state machines to read), Scope (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by) src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry (with the side a one-way collider blocks from, and the rectangle around a shape a broad phase looks at first, #184), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both) src/core/util/ list (take out, put back and move an item — what a command's undo is made of), vec (dot, the closest point on a segment — shared by the collider maths, the outline hit area and the polygon grips) -src/core/commands/ undoable commands, CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts +src/core/commands/ undoable commands (pixelArtCommands: one command runs any pixel art edit and keeps the state before it, #130), CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts src/core/graph/ graph model, NodeRegistry/NodeContext/values (a travelling list may hold live objects, `storedList` narrows one down to what a PropertyBag may hold, #157), interpreter, dropTarget, nested (behaviors as nodes), emitGraph (the walk both graph compilers share: each node once, in dependency order; cycles and property names reported), @@ -117,6 +117,9 @@ src/viewport/ view (transforms), items (what to draw), symbols (how to resolution; registers vector art in both halves of the picture path; PaintCache keeps a painter's gradients and hands the ones it no longer draws to Pixi's texture garbage collector, and a texture fill waits for its picture instead of drawing without it, #186), + PixelCels (the pixels of pixel art in memory: a painted cel is named at once, the ones the + server lacks are uploaded before a save, #130) + pixelArtPicture (every frame composed on the + CPU and laid side by side onto one canvas, so a frame is cut out like a texture frame), SoundLibrary, ShaderLibrary, ParticleLibrary, particles (one ParticleContainer per source), tilemap (one container per layer), drawOrder (objects and tilemap layers by z, #174), activeCamera — no editor concepts, @@ -171,6 +174,11 @@ src/editor/graph/ GraphEditor (header, breadcrumb), GraphCanvas, graphView src/editor/container/ ContainerEditor (header), ContainerCanvas (a SceneViewport on a template) src/editor/texture/ TextureEditor (header), TextureCanvas (picture + frames), SliceDialog (a FormDialog), GenerateCollisionDialog (#161: the rows, and a summary that traces while the numbers change; the texture library hands it the sheet's pixels), sheetView (the grid maths), ImagePicker + imageGrid (the "+ → Texture" dialog), checker (the transparency board) +src/editor/pixelart/ PixelArtEditor (header: Resize…), PixelArtCanvas (one frame composed as the picture path composes it, + whole zoom steps, grid, onion skin and tile preview; a stroke paints a copy and becomes one command), + PixelArtSide (tools, palette, layers, frames, view, preview), PaletteGrid, PixelPreview, ResizeCanvasDialog, + pixelActions (what the panel does; merge and resize work out the new cels), pixelView (zoom steps, the pixel + under the pointer), celUploadStore (the project store that uploads the cels before it saves) src/editor/vectorart/ VectorArtEditor (header: the three tools, the picked point's type or, with Paint, whose handles, and the magnet), VectorArtCanvas (the drawing painted from core/model/vectorPath onto its own Graphics, with points and handles over it; the pen puts points down, the arrow drags points, handles and whole shapes, #129; the Paint diff --git a/Development.md b/Development.md index 038c129..306b147 100644 --- a/Development.md +++ b/Development.md @@ -13,7 +13,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs npm run build # writes dist/ and regenerates index.html ./build.sh # the same under `set -o pipefail`, for callers that pipe the output php tools/make-user.php # an account; every tool signs in (see Accounts) -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1232 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1259 editor checks, screenshots in /tmp/gm-e2e-*.png GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step ./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh status # live progress, or the last run's result diff --git a/Home.md b/Home.md index 5a01c54..19e8ebb 100644 --- a/Home.md +++ b/Home.md @@ -18,6 +18,7 @@ Live: · Widget playground: