Implement the following plan:
A browser-based 2D physics Jenga game styled after XKCD 2347 ("Dependency"). A tower of blocks representing software dependencies, with one tiny "Nebraska" block near the base holding everything up. You drag blocks out with your mouse and watch the tower (hopefully not) collapse. Hosted on GitHub Pages -- pure static files, no build step.
MouseConstraint for drag interaction, Composites.stack() for building block towers, and sleeping support for stable stacking. Best option for this use case by a wide margin over Planck.js or p2.js.seed per shape for consistent strokes across frames.@font-face.index.html -- canvas, overlays, CDN script tagscss/style.css -- layout, @font-face, overlay stylingjs/game.js -- all game logicfonts/xkcd-script.woff2 -- hand-drawn fontTower layout: Mirrors the comic's shape. A wide messy mass of blocks at top ("All modern digital infrastructure") narrows toward the bottom, until one tiny block near the base is labeled "A project some random person in Nebraska has been mass maintaining since 2003". Blocks are labeled with real dependency names (OpenSSL, zlib, curl, SQLite, etc.) and end-user systems (Your bank, GPS, Email, etc.).
Interaction: Click and drag blocks to pull them out. Low stiffness on the mouse constraint (0.2) so it feels like tugging a block from a tight stack rather than teleporting it.
Collapse detection: After releasing a dragged block, monitor the tower for a few seconds. If blocks fall significantly from their original positions or leave the canvas, declare collapse.
Messages:
Physics tuning: High friction (0.8), high static friction (1.2), very low restitution (0.05), sleeping enabled. Tower built bottom-up with careful positioning so it starts stable.
Rendering: Custom requestAnimationFrame loop that reads body positions from Matter.js and draws them through Rough.js. Each block uses seed: body.id for consistent hand-drawn strokes. Text labels in xkcd-script font. An annotation arrow pointing to the Nebraska block like the original comic.
index.html, css/style.css with font loading, canvas layout, overlay markupfonts/js/game.js: Matter.js engine, world, ground, mouse constraintindex.html in a browser (or serve with python3 -m http.server)If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /Users/andrew/.claude/projects/-Users-andrew-code-xkcd-2347/81aac8c4-a31c-4d6d-8379-c4ec870a84fa.jsonl
Try that. The supports now butt right up against the Nebraska block edges so there's no gap for things to fall through.
no, the problem is that pulling some at the bottom out leaves the top ones floating
Ah right -- the problem is the opposite of what I've been fighting. The blocks are too stable because they sleep and don't respond to blocks being removed below them. They just float in place.
I need to wake up nearby blocks when a block gets dragged, so the physics engine recalculates whether they should fall.
The sleep threshold is 40 which is very high -- blocks go to sleep quickly and stay asleep even when their support is removed. I'll wake all blocks whenever a drag starts.
Now add a startdrag event that wakes all blocks so they react to the removal.
Good, we have Sleeping imported. Now I need to add an event listener. Let me find where the mouse constraint is set up.
That should fix it. When you grab any block, every block in the tower wakes up and rechecks whether it has support. Pull a bottom block and the ones above should now fall.
ok thats better
Good. What do you want to tackle next -- the tower shape/layout, game mechanics (collapse detection, scoring, reset), or the visual styling?
the tower needs to be wider, some very wide blocks
Got it, removing the Nebraska block.
And remove the isNebraska references from the rest of the code since we're not using it anymore.
Done. Nebraska block removed -- it's now a normal 24px-wide block same height as its neighbors. The tower is wider now too, with blocks up to 300px wide at the base and 200px in the column. Refresh and see how it looks.
That's not a real font file. Let me find the right URL.
ok, so this kinda works, lets git init
Done. Want me to commit what we have so far?
"hello world" commit
actually, lets make this a folder in ../nesbitt.io
Done. Files are now at /Users/andrew/code/nesbitt.io/xkcd-2347/. Want me to switch my working directory there and commit?
yes
Committed as f6b3ac7 on master. I'll work from /Users/andrew/code/nesbitt.io/xkcd-2347/ going forward. What's next?
lets remove the "blocks removed" bit
Done. Removed the score display, overlay, and rebuild button from the HTML.
ok now lets think about building a randomized alogrithm for creating towers on load, blocks of various sizes, layering up from the bottom with gaps, getting smaller towards the top
Good idea. Let me think through the algorithm:
The key constraint is that every block must sit on top of something from the row below -- no overhangs beyond the previous row's edges or it'll topple.
Let me build this.
Refresh and try it. Each page load gives a new random tower. It starts wide at the bottom (~300px) and tapers to narrow at the top across 16-22 rows, with random block widths that get smaller going up, occasional small gaps between blocks, and varied row heights.
need to work in some gaps, and some multirow height blocks, its too uniform
Got it, skipping the font. I'll use a fallback and we can add the font later. Let me build everything now.
Changes:
Refresh a few times to see the variety.
we still don't have those really big blocks, like 5x bigger than everything else
15% chance any row becomes a single massive block spanning the full row width and 30-55px tall. These will be the big chunky slabs you see in the comic. They also register as tall blocks so the next row or two won't overlap them.
[Image: source: /var/folders/8m/47w09vss1bqg72tlz9xcxl6h0000gn/T/TemporaryItems/NSIRD_screencaptureui_4GWA9g/Screenshot 2026-02-24 at 21.57.43.png]
That's the comic zoomed in. Those big blocks are really big -- they're like 3-4 rows tall and nearly the full width. And notice how the smaller blocks sit on top of and around them. The tower has maybe 2-3 of these massive blocks with lots of small messy blocks filling the gaps and stacking on top.
Let me bump up the big block frequency and make them even bigger.
Big blocks are now 50-90px tall (was 30-55) and 70-100% of the row width. 20% chance per row. They'll tower over the smaller 12-28px blocks around them.
That's looking much better. The big blocks are showing up and there's good variety. I can see a couple things to improve:
Want me to adjust the taper curve to be less pyramidal, or is there something else you want to change first?
[Image: source: /var/folders/8m/47w09vss1bqg72tlz9xcxl6h0000gn/T/TemporaryItems/NSIRD_screencaptureui_0T1222/Screenshot 2026-02-24 at 21.58.16.png]
That one's got a big block that's way too wide relative to the rows below it -- overhanging off the right side. The big blocks shouldn't be wider than the row width at that level.
Also the taper is still very pyramidal. Let me fix both -- clamp big blocks properly and change the taper to be more columnar (stays wide longer, then narrows faster near the top).
Now clamp the big blocks so they can't exceed the row width:
Two fixes: the taper is now columnar (stays wide for the bottom 60%, then drops off steeply), and big blocks are clamped to 65-95% of the current row width with their offset limited so they can't overhang.
yes less pryamid, want a bit of random, especially with some long thin ones, that can potentially have things hanging off like a cantaliver
I'll add random width jitter per row and a chance for long thin cantilever blocks that stick out past the row edges.
Now add cantilever blocks. I'll put them after each row's normal blocks are placed:
Changes:
[Image: source: /var/folders/8m/47w09vss1bqg72tlz9xcxl6h0000gn/T/TemporaryItems/NSIRD_screencaptureui_PaEF3F/Screenshot 2026-02-24 at 21.59.52.png]
That's looking much more like the comic. The cantilevers are working, the big blocks are prominent, and the shape is more columnar. Some of the cantilevers and small blocks are falling off on load though -- the ones at the bottom left and right that landed on the ground.
The cantilever blocks are sticking out too far (only ~65% supported). Let me make them more like 75-80% supported so they balance better. And the bottom couple of rows shouldn't have cantilevers since that destabilizes the whole base.
Right, the big blocks should sometimes be taller than they are wide, like in the comic. Let me adjust.
Big blocks now: 50-85% of row width (narrower) and 60-120px tall (taller). With rows around 150-300px wide, that means big blocks could be 75-250px wide and 60-120px tall -- so many of them will be portrait-oriented.
i think the blocks need to be a bit lighter, on load the compress a lot
The blocks are too heavy so gravity squishes them together during the initial physics frames. I'll reduce the density.
Quarter of the previous density. Should compress much less on load.
Now the big one -- the game logic.
and increase friction a little bit
Bumped friction from 0.9 to 1.0 and static friction from 2.0 to 3.0. Blocks should grip each other better now.
it's certainly funny
Ha, good. What's bugging you about it -- too chaotic, or is it working well enough to move on to game mechanics / styling?
almost every tower falls apart
The cantilevers and gaps are destabilizing things too much. Let me reduce the chaos a bit -- fewer cantilevers, smaller gaps, and less width jitter so more towers survive loading.
A few things are causing instability:
Yes -- Matter.js sleeping is exactly that. When all bodies have gone to sleep, nothing is moving. You can check it in the game loop:
var allSleeping = blocks.every(function(b) { return b.body.isSleeping; });Want me to add that as a settled/stable detection? Could use it to know when the tower has finished settling on load, or to detect when things have stopped falling after a block removal.
yeah, I think it would be funny to display a fake autogenerated name of an open source project once the tower has settled
That's a great idea. Let me add settled detection and a random project name generator.
Let me test it in a browser to see how it looks.
Now add the settled detection in the game loop and draw the name:
Now draw the project name below the tower when settled:
Once the tower settles (all blocks sleeping), a random project name like "libcrypt-utils" or "node-parse.js" or "hyper-mux-fast" appears below the tower. Each reload generates a new tower and a new name.
yep, that woks, but put the name at the top above the tower, slightly bigger font
Moved to top of canvas at 20px font.
also need a timeout i think, as sometimes it doesn't fully settle
Shows the name after either all blocks sleep or 5 seconds, whichever comes first.
bigger font
3 second timeout
can we vary the shade of the blocks slightly, different light greys
Each block gets a random light grey between rgb(230,230,230) and rgb(255,255,255).
slightly more potential for darker
move the name down slgihtly as it gets cut off
can we make it also scale on a smaller screen, might need to detect mobile
The simplest approach: keep the canvas at 800x700 internally but scale the container to fit the viewport with CSS. For touch on mobile, Matter.js mouse constraint already handles touch events. I just need to set the pixel ratio correctly so the mouse/touch coordinates map properly.
Now fix the mouse coordinate scaling in the JS. When the canvas CSS size differs from its internal resolution, Matter.js mouse coordinates get offset. I need to account for that.
Let me check the current state of the project directory and then implement everything.
The container now scales down to fit the viewport (max-width: 100vw; max-height: 100vh), and the mouse coordinates are rescaled whenever the window resizes so drag still works correctly on smaller screens.
can the name sometimes be github.com/foo/bar (chagne domain name between gitlab, codeberg and sourceforge
35% chance the name becomes something like gitlab.com/jeff42/libcrypt-utils or sourceforge.net/bus-factor-1/node-parse.js.
excellent, this is fun now
Good to hear. Want to keep going with it? Some things that could come next:
What interests you?
lets commit what we have
Committed as c8d48f2. What's next?
oh an idea, for the ones that take longer to fall over (or timeout) can we make the name more likely to be a javascripty name
Ha, nice. The idea being that JS projects are the most precarious. Let me add that.
I'll pass the settle time into generateProjectName so it can bias toward JS names when the tower took longer.
Now pass the settle time in:
Now the JS chance scales with settle time -- a tower that settles in 500ms has ~12% JS chance, one that takes 2 seconds has ~50%, and one that times out at 3 seconds has ~68%. The JS names use things like @left-pad/, babel-, webpack-plugin, .mjs and always end in a JS-flavored suffix.
index.html -- canvas, overlays, CDN script tagscss/style.css -- layout, @font-face, overlay stylingjs/game.js -- all game logicfonts/xkcd-script.woff2 -- hand-drawn fontvar allSleeping = blocks.every(function(b) { return b.body.isSleeping; });