Starforge
- 12 Devlogs
- 102 Total hours
Draw, animate and export pixel art in your browser!
Draw, animate and export pixel art in your browser!
⭐ Starforge is a pixel art & animation studio that runs in your browser.
Last time multiplayer rooms forgot everything you did the moment your wifi died. Now you can paint offline those strokes stays saved forever.
Undo got also a treatment. It only ever takes back your own pixels now. The test for that one is my favorite in the whole repo, btw. Also you can now undo on phone with a two-finger tap, and see all other gestures in the new keys help menu.
The relay grew up too. 60 ops and 256 KiB per second per connection, 2048 concurrent sockets, and then a security fix: a pixel count that used to allocate gigabytes before failing now throws first, ?relay= to a foreign host gets ignored outside local dev, and a dead branch that would have closed the whole room is gone.
The Starforge will now soon get out Vercel to be self-hosted on my Pi
, so you can actually test once i deploy and ship 
Receipts:
Next: the ship!
As a prize for your great work, look out for a bonus prize in the mail :)
⭐ Starforge is a pixel art & animation studio that runs in your browser.
Today you can send someone a link and draw together!
/r/<id> opens a shared room. Two browsers sharing the same canvas. Close both windows, come back later, and the room is still there.
The interesting bit is that the relay doesn’t need to remember everything forever.
Every stroke gets a sequence number. The latest canvas is periodically folded into a snapshot, while the last 1000 operations stay around for catch-up. Rejoin after a short absence? Replay the missing strokes. Gone for too long? Get one fresh snapshot instead.
Rooms have a few hard edges too: max of 100 rooms in memory, 14-day expiry and 20 creations per IP per hour.
Receipts:
Next: making reconnects less forgetful.
⭐ Starforge is a pixel art & animation studio that runs in your browser.
Last time the relay queue was in memory. Now it is a real wire using native WebSocket to talk with the relay I wrote by hand on node:http, handshake, framing, mask, fragmentation, ping/pong, close codes, all of it. Draw a horizontal stroke here, a vertical one there, and both canvases land on the same pixels.
900 pixels travel in 2,730 bytes. Raw it would be 7,200 (8 bytes per pixel: u16 x, u16 y, u32 color). On the wire the cells sort by (y, x), coordinates go as zigzag deltas, colors go as an index into a 1-entry table. That is 3.033 bytes per pixel, and every one of the 19 operation kinds round-trips byte-for-byte. The server does not trust a single byte, each op is validated with the same applyOperation the editor uses, on a clone. A bad op earns an ERROR and the connection stays open.
Receipts:
Next: the ephemeral room becomes shareable rooms /r/<id>
⭐ Starforge is a pixel art & animation studio that runs in your browser.
Since last week Starforge learned how to disagree without falling apart, and got more comfortable on tablets along the way.
This is the first piece of multiplayer, two complete editors, two independent copies of the same drawing, and a queue between them. There is no server yet. I can draw in both editors, hold every message back, reverse their order, deliver one twice, and the documents still finish on the same pixels.
Alongside that invisible foundation, community conversations pointed at five visible workflow breaks: the compact tablet layout only kicked in below 720 px, colours sat with layers and frames instead of the brush, palette authoring bounced between two dialogs, brush opacity used a tiny track while layer opacity had a fine large slider, and precise add/subtract selection hid behind keyboard modifiers touch users never see. All five are fixed in this pass.
Next: the queue becomes a real wire, WebSocket handshake will be written by hand on node:http with the relay on a Raspberry Pi.
⭐ Day 18 of building Starforge, a pixel art & animation studio that lives in your browser.
That heart is only half real. I drew the left side and the editor drew the right side under my pencil, live. Mirror mode landed today, and I spent an embarrassing amount of time just watching the other half appear.
Two more things came with it. The Export dialog now plays your actual GIF before you download it, next to its true size, same bytes as the download, so the dialog can’t lie to you. My greeting star comes out at 2.4 KB, using 125 of 256 colors. And the encoder learned to lose colors gracefully, which is my favorite thing I’ve built this week:
A GIF holds at most 256 colors. Until today, art with more colors hit a wall, an error message asking you to reduce your palette. I hated that message, so now the encoder runs median cut instead.
Median cut, in human terms: pour every color in your drawing into one box. Find the box’s longest side, red, green or blue, whichever stretches widest, sort the colors along that side, and cut the box in half at the median. Repeat, always cutting the widest box you have left. After 255 cuts you hold 256 boxes, and each box’s average becomes one crayon in the final palette. Colors that sat close together end up sharing a crayon, which is exactly the kind of loss your eye forgives.
The sneaky part is that the same drawing must produce the identical file every single time, so every sort breaks ties by the color’s own value and every average rounds the same way. There’s a test that encodes the same art twice and demands the bytes back, identical.
Receipts
Last post I asked you to export a GIF and drop it in the comments. Nobody did :C, fair enough, the star was doing all the work.
So today I made it personal: open starforge.lacorte.city, turn on mirror mode, and draw half a thing. The editor does the other half. Post what you get, I’ll feature my favorite in the next devlog :D.
And a real question I can’t decide alone: diagonal axes or kaleidoscope. which should mirror mode learn first? Both are on the backlog. You can decide the order.
(If you want to catch the next post, follow the project, that button actually notifies you.
)
⭐ Starforge is a pixel art & animation studio that runs in your browser.
The GIF above was drawn, animated and exported inside Starforge.
Yesterday I said I was writing the GIF encoder by hand. It’s finished, and already sitting behind the Export button in the editor.
A GIF doesn’t store pixels. It stores a conversation between two sides that build the same notebook and never send it.
The encoder walks the image. The first time it meets a run of pixels it spells the run out, then writes that run in a notebook next to a number. The next time the same run shows up, it sends the number instead of the pixels. The decoder reads those numbers and builds the identical notebook as it goes, which is why the notebook itself never travels, both sides derive it from the same stream.
That’s LZW, and it’s lovely right up until you notice the decoder is permanently one entry behind. It can’t file a new run until it has read the code that comes after it. So when the notebook outgrows the current code width, the two sides widen at different moments, and the encoder has to wait one step longer than feels correct. Get that off-by-one wrong and the file is perfect for a few hundred bytes and then dissolves into confetti. That was my morning.
Receipts
Draw something at starforge.lacorte.city, export the GIF, and drop it in the comments!
I’ll feature my favorites in the next devlog :D.
⭐ Starforge is a pixel art & animation studio that runs in your browser.
Yesterday I said those numbered cells at the bottom of the screenshot were today’s story. Here they are: every cell is a frame with its own duration in milliseconds, play runs the reel on the document’s own clock, and since today, the frame you’re drawing shows its neighbours behind it as ghosts. The star that greets you is a loop with 4 frames now, so you can already see the animation.
Receipts:
Next: I’m writing a GIF encoder from scratch. By hand. :D
⭐ Starforge is a pixel art & animation studio that runs in your browser.
The first ship reviews asked for three things: custom colors (two voters), a less twitchy zoom (one), and opening files (one). All three landed this week. The Color Studio mixes any color in HSV, hex or per-channel RGBA and drops it straight into your palette. The zoom got tamed (story below). And you can now save and open editable .starforge projects, including inside a Portable PNG, a real image with the whole project embedded in it, so the file you post is also the file you can keep editing.
A Mac voter said the zoom flew. They were right, and the bug was pretty interesting (it also ate my whole morning). One click of a mouse wheel arrives as a single event of about 100px, but a trackpad glide arrives as dozens of tiny ~4px events. The old code stepped the zoom once per event: a mouse user got one clean step per click, a trackpad user got twenty-five. And I didn’t even know pinch was part of the bug until I read how Chrome handles it: a pinch is faked as ctrl+wheel, so it lands in the same handler too.
The fix: accumulate to 100, then discard the remainder. Every event banks its delta. A step only fires when the bank crosses a notch, and turning around mid-glide resets the bank instead of cancelling a step you already saw.
Receipts:
Those numbered cells at the bottom of the screenshot are tomorrow’s story :D
⭐ Starforge is a pixel art editor that runs in your browser. No engine work today, since I focused on a more important point: Whoever opens starforge.lacorte.city from a feed post, leaves in about two minutes, with no instructions and usually a phone in their hand, so the whole day went into that first visit. You can start your own document now insted of only erasing the star, your drawings survives a reload, and on a phone the layer panel became a drawer.
Most web editors take your drawing with them when they break. This one doesn’t. When a component throws, an error boundary renders a screen where Export PNG still works, if the editor dies, you get the PNG out before you reload.
Two reasons that’s possible. The document never lived inside the component that broke: it belongs to the DocumentSession, outside the render tree. And the export never goes through the editor, it composites the frame straight from the document. The thing that holds your art and the thing that failed are not the same thing.
Tomorrow morning the first real ship goes out :D
⭐ Starforge is a pixel art editor that runs in your browser. Yesterday it could only draw in a single surface. Today the same old star is five stacked layers, carrying its own opacity, visibility and lock, plus a rule for how its colors blend with the layers below.
Compositing every layer on every frame can’t hold 60fps. At 256x256 with eight layers at 800% zoom that’s eight blits of 2048×2048 per frame, about 33 megapixels of work to show a picture that didn’t change. So the editor composites once per change into an offscreen canvas, and the screen just draws that one image. Rendering with no cost since that nothing changed.
All with 169 green tests.
⭐ Starfoge: A multiplayer pixel art & animation studio that runs in your browser.
Day three, and it finally draws! The whole toolbox landed with six drawing tools and keyboard shortcuts already known by pixel artists. Yesterday’s 37 green tests are 117 today.
Today i want to deep dive in one of the questions i had: why fast strokes come out dotted. The browser only tells the page where the mouse is once per screen frame (~60fps), but a hand can cross half the canvas between two of those reports and a quick zigzag would become a trail of separated dots.
The fix comes in two layers:
First, the browser actually keeps the positions it skipped and hands them back in a bundle with each event, so I ask for those in-between samples.
Second, even those have gaps when the gesture is really fast, so between every pair of samples I draw a tiny line using Bresenham algorithm, the same grid walker my line tool uses, it steps from one pixel to a touching neighbor and can’t skip a cell by construction. So every stroke you see is secretly dozens of tiny straight lines, stitched end to end.
Tomorrow i will start landing Layers to our studio. Thanks for reading and see you on the next devlog :D
⭐ Starforge is alive in the browser! This is day two building a multiplayer pixel art studio. So far the editor opens with a little star, and every pixel stays razor sharp at any zoom. With 37 green tests I already started coding the first drawing tools (pencil, eraser, bucket).
The sharpness comes from a two stage blit with shared memory. A sprite’s pixels live in one Uint8Array wrapped in an ImageData view, using only one buffer for model and image bytes. Stage one pushes just the changed rectangle onto an offscreen canvas (1:1) which ignores scaling and smoothing so it never blurs. Stage two is a single draw image that stretches the offscreen canvas to the current zoom with the GPU doing the nearest-neighbor upscaling for free. When the pencil lands next ship, the bytes it writes are already the bytes the renderer reads.