You are browsing as a guest. Sign up (or log in) to start making projects!

lacorte

@lacorte

Joined August 2nd, 2026

  • 15Devlogs
  • 4Projects
  • 3Ships
  • 35Votes
hello! :D
Open comments for this post

7h 32m 27s logged

⭐ 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 :raspberry_pi: , so you can actually test once i deploy and ship :boykisser:

Receipts:

  • 776 unit tests green
  • 43 Chromium
  • zero production dependencies in the core

Next: the ship!

0
0
6
Open comments for this post

13h 19m 21s logged

⭐ 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:

  • 708 unit tests
  • 25 Chromium E2E
  • zero production dependencies in the core

Next: making reconnects less forgetful.

1
0
65
Open comments for this post

8h 39m 56s logged

⭐ 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:

  • 662 unit tests green
  • 23 Chromium E2E
  • zero production dependencies

Next: the ephemeral room becomes shareable rooms /r/<id>

0
0
13
Open comments for this post

9h 34m 36s logged

⭐ 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.

Receipts

  • 633 green unit tests, with 4 ffmpeg cross
  • four pinned seeds × 100 generated operations across three replicas, including pixel conflicts, scalar edits, palette replacement, layer/frame creation, deletion and ordering
  • 20 Chromium E2E tests pass
  • receiving and applying a 900-pixel patch averages about 0.49 ms on my development machine (2,034 patches/second in the recorded run)
  • zero production dependencies in the core

Next: the queue becomes a real wire, WebSocket handshake will be written by hand on node:http with the relay on a Raspberry Pi.

0
0
25
Ship

⭐ Starforge is a pixel art & animation studio that runs entirely in your browser. Open (starforge.lacorte.city)[https://starforge.lacorte.city] and draw, as of this ship, your drawing moves.

Ship 2 is the Animation Update.

Animate: a frame timeline where every frame carries its own duration in milliseconds. Add, reorder and duplicate frames, play the reel on the document’s own clock, and draw with onion skinning.

Export: an animated GIF encoder written byte by byte, from the GIF89a header to the LZW dictionary resets. When your art has more than 256 colors, a hand-rolled median-cut quantizer folds it into a legal palette instead of refusing. The Export dialog plays the actual GIF before you download it, next to its true byte size. Spritesheets come out of the same door.

Draw: mirror mode, switch on an axis and the editor paints the reflection live under your pencil. The Color Studio mixes any color in HSV, hex or per-channel RGBA. Trackpad zoom got tamed: wheel deltas accumulate into clean notches instead of stepping twenty-five times per glide.

Keep your work: editable .starforge project files, and Portable PNG, a real image with the whole project embedded inside, so the file you post is also the file you keep editing. Autosave still catches every stroke, and even a crash screen can still export your drawing.

Under the hood: 563 tests, zero dependencies in the core. The encoder is proven against ffmpeg that decodes my GIFs and every
pixel comes back identical, including a noise frame built to overflow the LZW dictionary. Quantization is deterministic, same art to the same bytes, and folds ~10,000 colors into 256 in about 23ms.

Where it’s going: next ship, the “multiplayer” in the tagline becomes real, a hand-rolled CRDT (last-writer-wins per cell, Lamport clocks) and shareable rooms.

Works on your phone. Shortcuts follow Aseprite. Press ? in the editor.

  • 4 devlogs
  • 34h
  • 18.98x multiplier
  • 618 Stardust
Try project → See source code →
Open comments for this post

9h 20m 12s logged

⭐ 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:

How it works

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

  • 563 green tests
  • ~10,000 colors in, 256 out, in about 23ms
  • ffmpeg decodes the quantized file, and re-encoding what it hands back produces the identical bytes

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. :boykisser: )

0
0
15
Open comments for this post

5h 16m 26s logged

⭐ 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.

How it works

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

  • 545 green tests
  • I encoded with my code and decoded with ffmpeg. Every pixel came back identical
  • 16 frames of 256×256 encode in 135ms
  • Browsers quietly treat any GIF delay under 2 centiseconds as 10, so the encoder floors every frame there.

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.

0
0
9
Open comments for this post

7h 55m 53s logged

⭐ 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:

  • 527 green tests
  • 3 recompositions parked with ghosts on
  • three new end-to-end tests in a real browser

Next: I’m writing a GIF encoder from scratch. By hand. :D

0
0
7
Open comments for this post

11h 56m 58s logged

⭐ 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.

Wheel deltas lie about intent

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:

  • 511 green tests
  • an e2e that draws, reloads the page and exports in a real browser
  • golden ink fixtures pinned by SHA256

Those numbered cells at the bottom of the screenshot are tomorrow’s story :D

0
0
25
Ship

⭐ Starforge is a pixel art studio that runs entirely in your browser. Open starforge.lacorte.city and draw.

This first ship is the whole editor core, built from scratch in TypeScript with zero dependencies in the core.

Draw: pencil, eraser, line, rectangle and ellipse (outline or filled), and a bucket fill with per-channel tolerance and a contiguous/global switch. Rectangular selection that lifts the pixels and lets you drag them anywhere.

Layers: add, duplicate, reorder, rename. Per-layer opacity, seven blend modes, visibility and lock.

Keep your work: undo/redo across pixel strokes and structural edits alike, autosave in the browser, and PNG export If the editor ever crashes, the error screen still exports your drawing.

Under the hood: 208 tests, including property tests that throw hundreds of random actions at the history and require a byte-identical document back. The renderer composites once per change, not once per layer: the cache key is two integers, and panning triggers zero recompositions. Flood fill, Bresenham lines, ellipse rasterizer, composito, all hand-rolled, no graphics libraries.

Where it’s going: the “multiplayer” in the tagline is the arc: a hand-rolled CRDT (last-writer-wins per cell with Lamport clocks) and shareable rooms are the next major ships.

Works on your phone. Shortcuts follow Aseprite. Press ? in the editor.

  • 4 devlogs
  • 29h
  • 17.82x multiplier
  • 498 Stardust
Try project → See source code →
Open comments for this post

5h 17m 1s logged

⭐ 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.

How it works

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.

Receipts:

  • 208 green tests
  • draw, reload, auto-save
  • a keys help menu for shortcuts and gestures
  • zoom by button, fit in one click

Tomorrow morning the first real ship goes out :D

0
0
50
Open comments for this post

6h 20m logged

⭐ 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.

How it works

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.

1
0
53
Open comments for this post

10h 44m 27s logged

⭐ 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

0
0
37
Open comments for this post

6h 18m 38s logged

⭐ 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.

1
0
99
Ship

lacorte.city is my personal website i rebuilt from scratch with an oldschool aesthetic and 88×31 badges. the site has i18n for english+ptbr, an rss feed and blog posts that can be written in markdown delivering automated structured data for search engine optimization.

live: https://lacorte.city
repo: https://github.com/Gabriel-Lacorte/lacorte.city

a.i helped to review and find gaps in my architecture, but all design decisions, typography choices, and code were written by hand.

Try project → See source code →
Open comments for this post

4h 52m 26s logged

the last couple of days, i reworked my portfolio whole visual identity and gave it an oldschool style, inspired by some references from neocities.org. i wanted the design to feel more personal and memorable, an style i already explored before in my old website (lacorte.neocities.org).

0
0
128
Open comments for this post

11h 22m 17s logged

Every time a Go context expired while sending a WhatsApp notification, the queue worker interpreted “deadline” as a network timeout and retried using a dead context. Result: wasted attempts, queue drain stalled. As a fix, errors.Is(err, context.DeadlineExceeded) and context.Canceled now return false BEFORE the textual fallback in isTransient(). The deadline is the caller’s decision, not a downstream failure. Now we will proceed to implement lazy-loading of groups such as “This Week” / “Later” on the dashboard.

0
0
20

Followers

Loading…