Tagged: personal

Graph Paper Lindenmeyer Systems

  • Posted in dev

When I was a kid I learned about Lindenmeyer Systems and the fun tree patterns they create. I even followed a Twitter bot that generated and posted random pretty lsystem renders.

These are similar to fractals, except unlike traditional fractals they don’t usually expand within a fixed space, they either continue to grow or loop back intersecting themselves.

Meanwhile I spent most of my time in school and needed something to occupy my hands with. I did a lot of notebook doodling, except people notice those. But I did have graph paper. And graph paper gives you enough structure to draw lsystems by hand. So I did, a lot.

graph paper photo

I probably went through a notebook every two years, and I can’t remember once ever drawing an actual graph. This was when I was a child in childish ways and hadn’t yet learned about dot paper. Oh, misspent youth....

There are a couple interesting things about this mathematically.

First, there’s only really one interesting space-filling pattern to draw on graph paper, which is forking off in two 45 degree angles. Anything that doesn’t fit on the coordinate grid (like incrementally decreasing line lengths) becomes irregular very quickly. The main option to play with different patterns is by setting different starting conditions. (Seems like it’d get boring quick, right? I spent the rest of the time trying to apply the Four color theorem in an aesthetically satisfying way. Still haven’t solved that one.)

Second, drawing on a coordinate grid means using Chebyshev Geometry, where straight lines and diagonal lines are considered the same length. So a circle (points equally far apart from a center point) is a square. This is also sometimes called chessboard geometry, because it’s how pieces move on a grid.

distances visualization1

Also, because I’m drawing this by hand, I can only keep track of visible tips. So if tips collide I count them as “resolved”, even if mathmatically they’d pass through each other.

But I kept hitting the end of the graph paper.

I was bored in a work meeting this week and ended up doing the same thing, and it made me wonder about the properties of the pattern. Did it repeat? Did it reach points of symmetry? I thought it would be fun to whip up a tool to run my by-hand algorithm to see.

There are lots of web toys for lsystems but none are designed with these graph paper constraints. So just as an exercise I built my own from scratch, and it’s pretty good at drawing my high-school pattern.

generation animation

The answer to the pattern question is: it depends on the starting position! If you start with one line you start to see radial symmetry, but if you start with two lines facing away from each other (as in the animation) it’s more interesting. Neat.

Each generation is considered a state frame that keeps track of lines (for rendering), buds (for growing the next iteration), bloomed_buds (for bud collision) and squares (for line collision).

For the Chebyshev geometry I just keep every line at a 1.0 length and let the trigonometry functions round up.

I didn’t actually implement a parser for formal Lindenmeyer syntax, I just defined everything recursively in pure Javascript, using a definition to encapsulate details about the pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const definition = {
  at(bud) {
    const lines = []
    const buds = []

    for (let angle of [
      bud.r - 0.25,
      bud.r + 0.25
    ]
    ) {
      const rads = Math.PI*angle
      let len = 1
      let xy = [bud.x + len*Math.sin(rads), bud.y + len*Math.cos(rads)]
      if (chebyshev) xy = xy.map(Math.round)
      lines.push(xy)
      buds.push({x: xy[0], y: xy[1], r: angle})
    }
    return {lines, buds}
  }
}

Then rendering is done recursively:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function get_frame(generation) {
  if (generation < 0) throw Error(`Invalid generation ${generation}`)

  if (!frames[generation]) {
    const frame = structuredClone(get_frame(generation - 1))

    const old_buds = [...frame.buds]
    const new_buds = []
    const new_squares = []
    for (const i in old_buds) {
      ...
    }
    frame.bloomed_buds = [...frame.bloomed_buds, ...old_buds]
    frame.squares = [...frame.squares, ...new_squares]
    frame.buds = prune_buds(new_buds, frame.bloomed_buds)

    frames[generation] = frame
  }

  return frames[generation]
}

There’s a slight complication to doing this cheaply with lines: on paper, there are cases where lines intersect, and in those cases I stop at walls, even if it means drawing a partial line. Doing that kind of collision detection here in vector space would be very complicated! And without it the pattern loops back on itself and doesn’t look as good, and definitely doesn’t match my notebooks:

missing collision

The solution for this was very simple collision detection. The final program keeps tracks of which squares are “occupied” and breaks branches any time they’d collide.

I uploaded it to my stash if you want to play with it. As an added constraint I wrote everything by hand in one single-file html page.

The one problem with the tool (besides being bare-bones) is it renders the output as an SVG instead of a bitmap on a canvas. This “feels right”: they’re lines, might as well make them vectors. But on high values browsers have performance issues rendering very large quantities of DOM elements.

I’d rewrite the renderer to draw to a canvas instead except I like the idea of the infinite SVG canvas specifically. The point was to escape the edges of the graph paper, remember?

But I’d already abstracted line drawing so the line coordinates were stored in state frames, so it was easy to write an alternate renderer using the canvas API.

https://stash.giovanh.com/toys/lsys.html


  1. Illustration by Cmglee - Own work, CC BY-SA 4.0, Link â†©

SUPERHOT VR's Story was Removed. What?

  • Posted in gaming

SUPERHOT VR released in 2017. Then in 2021 the game’s entire story was removed.

What’s happened here is fascinating, but somehow nobody has talked about it seriously. Because it’s censorship in a video game — a topic the gaming community cannot be normal about — it is nearly impossible to even think about the issue through all the noise. Anyone aware of this topic at all seems to be screaming about Woke, or complaining about games becoming “political”, as if “political” is just a switch you can throw to make media worse.

Wikipedia summarizes the discourse as:

The choice to remove these games led to the game getting review bombed on Steam, with some users claiming that Superhot Team was giving in to “snowflakes” and others believing it to be a form of virtue signaling

But this is insane! A historically significant VR game — one of the greatest of all time — had one of its defining characteristics removed, without any explanation or replacement. This isn’t some Stellar Blade fake controversy, something weird happened here. There are real, understandable things to object to, and none of them are right-wing culture war buzzwords.

But what is SUPERHOT?

SUPERHOT was originally developed for the 2013 7 Day FPS Challenge game jam by Polish team “The Bricky Blues”, directed by Piotr Iwanicki. In September 2013 it was released on the Blue Brick Software and Embedded Systems website in three separate “episodes” because the levels were developed in parallel in three separate unity projects for the jam.

After the demo received positive feedback, SUPERHOT went to Kickstarter (after they got Kickstarter to support Poland) and was successfully overfunded in June 2014. (With the success of SUPERHOT, the Blue Brick company seems to have been abandoned.) SUPERHOT (2016) was then released in February.

The Last Clockwinder Retrospective

  • Posted in gaming

I played The Last Clockwinder last week, and it changed the way I think about production games.

Factory games

The Steam page describes The Last Clockwinder as a “VR puzzle-automation game.” I like production and automation games. But I’m used to FTB and Factorio and Zachtronics and Universal Paperclip. I’m used to the look automation-production games gravitate towards.

Factorio

Factorio’s top-down design invites you to create sprawling factories that completely overtake the landscape. What little detail there is in the landscape is purely mechanical; resources you can extract and process, or enemies you have to either avoid or exploit for more resources.

Positioning the camera to give yourself a comfortable view of the structures you build and the items you’re manipulating leaves the actual character as a tiny focal point; more of a crosshair than a character or even an avatar.

Factorio scales enemy difficulty to “pollution” but this is always designed to be overcome, not be a legitimately limiting factor.

Infinfactory

In Infinifactory, you’re captured by aliens and forced to engineer efficient factories in exchange for food pellets. Each puzzle takes place in a set of stark, desolate environments. It’s first-person, but you never directly interact with another character; the most you get are notes about how much your predecessors hated it.

As soon as you solve a puzzle, you’re presented with a histogram: how could you optimize your solution further? Could you be faster? Use fewer blocks? Are you better than your friends, or falling behind?

Universal Paperclip (gif)

Universal Paperclip’s minimalist HTML interface makes it a graphical outlier, but the bare-metal minimally-styled HTML invokes a sense of brutalism that reenforces the game’s theme of efficiency in the pursuit of a goal to the exclusion of everything else.

And then there’s The Last Clockwinder.

The Last Clockwinder

It’s undeniably hard sci-fi. The first thing you do is arrive in spaceship. Throughout the game you’re on the radio with your friend idling in orbit, and the whole story revolves around interplanetary travel.

But then the first thing you see is a tree-patio with a hammock. It almost feels like a treehouse. The purpose of the tree is archival and preservation of rare and culturally significant plants; it’s a reserve, and that’s what gives it such importance. Inside the tree is the one room you stay inside for the entire game, and it’s a living space.

Events in games bother me

  • Posted in gaming

I don’t like “events”. I don’t like it when things are limited with requirements of spacial presence and time. I don’t like experiences that only exist in one moment and then can never be relived. I don’t like ephemera. I prefer things. Toys I can play with, tools I can use, books I can read, movies I can watch, all at my own discretion. I have agency over my things. The actual lived experience from occurrence to occurrence is always different, of course, but the externalities can be repeated. I love being able to preserve the essence of a thing.

It’s one of the reasons I like computers. Or maybe it’s a psychological trait I developed because I had access to computers growing up. It probably is, I think. But either way, I love the purity of digital storage and interface. I love having an environment where experiences can be preserved and replayed at my discretion without my having to make any demands on other people.

And so that’s one of the reasons I love video games. Their mechanics are defined and can be understood and mastered. Their levels are defined and can be understood and mastered. Despite the extreme rates of “churn” — video games go out of print much faster than books or other physical media — the software is digital, and can be saved, stored, and replayed. I can look up the flash games I played as a kid and replay them, exactly as they were, and understand myself a little better for it.

Of course there are exceptions; it’s impossible to have a multiplayer game without an implicit demand that other people play with you. When an old game “dies”, it’s often not because the necessary hosting software is being intentionally withheld, but that there just isn’t a pool of people casually playing it like there used to be. That’s still a loss, and it’s sad, but that’s an unavoidable reality, and it’s not nearly as complete a loss as a one-off event being over.

So I don’t like when games force seasonal events on me. Limited-time events introduce something new, but they also necessitate the inevitable loss of that thing. And that assumes you were playing everything from the start; events introduce content that can be “missable” in a meaningful way, so if you’re weren’t playing the game at the right time, even if you own the game and finish everything you can access your experience can still be rendered incomplete. One of the things I like about games is that they’re safe, and the introduction of time-based loss compromises that safety.

That constant cycle of stress and pressure to enjoy things before they were lost is one of the main reasons I stopped playing Overwatch. I realized the seasonal events in particular weren’t good for me; they turned a game that should have been fun into an obligation that caused me anxiety.

But I’ve been thinking about this lately not because of Overwatch, but because Splatoon 3 is coming out soon. Splatoon isn’t nearly as bad as all that, I don’t think it’s deliberately predatory aside from Nintendo’s standard insistence on denying people autonomy. Splatoon 3 invokes that “people will stop playing Splatoon 2” loss, but even before that, Splatoon (a game I love) left a bad taste in my mouth because of its events.

Alma Mater

I went to my old university today.1 I wanted to use the library.

It was a strange experience. There were things about my time there I missed, but I didn’t miss my time there. There was too much wrong. Ways I didn’t fit.

I looked around. It was passing period, and there was a throng of students coming and going both ways. The pavement was nice, new construction. People were laughing and talking and introducing each other.

Was I wrong? Should I be missing this? There is still so much good here. So I asked myself what it was I saw, exactly. And I looked out.