Jul 10, 2024
I've been slowly reading "The Nature of Order" by Christopher Alexander and slowly thinking about how to make my editor for text and line drawings more timeless. (And mostly sleeping a lot, if I'm honest.) Today the combination of the two led me to draw this shape for the line-wrapping algorithm.

photograph of a page from a notebook showing a strange shape a little bit like an axe or an old-style double-edge safety razor blade. It's straight on the left with rounded corners. On the right, is a bumpy, irregular 'blade' with 5 protrusions of irregular lengths. Four of them form two pairs of long+short protrusions, and a fifth is off to the bottom. The four protrusions are for code related to lines of text, labeled:
- at word boundary, need to wrap
- at word boundary, no need to wrap
- word, need to chop
- word, no need to chop

Until now I've been developing the editor the "usual" way, which for me consists of needing some computation, figuring out the most convenient place to perform the computation, then squirreling away the result somewhere it's available when needed. In an effort to get myself out of the rut of the inevitable problems of indirection and cache invalidation that result, I've been trying to replace all my ad hoc data structures with on-demand computation based on the base state of the program. And what I've been ending up with is umpteen variations of this pictured algorithm, just with different code stuck on to the protrusions.

There may be an abstraction that comes out of all this, but I don't see it yet. And as CA says, a flower isn't made up of identical petals. Each one evolves uniquely as a part of the whole.

Here's the Lua code skeleton corresponding to that drawing. The ellipses correspond to protrusions in the drawing:

for line_index, line in array.each(State.lines, State.screen_top.line) do
  if line.mode == 'text' then
    local initpos = 1
    if line_index == State.screen_top.line then
      -- top screen line
      initpos = State.screen_top.pos
    end
    for pos, char in utf8chars(line.data, initpos) do
      if char:match('%s') then
        if line_wrap_at_word_boundary(State) then
          ...
        else
          ...
        end
      else
        if x+w > State.right then
          ...
        else
          ...
        end
      end
    end
  else  -- drawing
    ...
  end
end

permalink

* *
Jul 3, 2024
This morning I'm thinking about turning my previous paper notation into a visual notation for something we typically use keyword args for. For example, the following glyph:

a drawing labeling the top, left, right and bottom coordinates of a rectangle in grey, and the height of a single letter 'h' within the rectangle

…might represent a function for initializing a text editor widget with the following signature:

edit.initialize(top, left, right, bottom, font_size)

And the numbers indicate a specific call to this function:

edit.initialize(15, 15, 115, 215, 20)

Interestingly, these alternative semantics would make for a more pleasing glyph.

edit.initialize(margin-top, margin-left, margin-right, margin-bottom, font-size)

a drawing labeling the top, left, right and bottom _margins_ of a rectangle in grey, and the height of a single letter 'h' within the rectangle

permalink

* *
Jun 30, 2024
Umpteenth attempt at stripping away unnecessary rules when teaching programming.

(Just a mockup to convey the idea. Plan is just to use this notation with pen and paper.)

Is this picture intelligible without any explanation?

permalink

* *
Jun 9, 2024
Here's the most recent bugfix I had to make, along with a fairly lengthy but still incomplete post-mortem in the commit description.

Still several things left to investigate.

  • Really small windows probably crash.
  • Really wide and short windows probably crash.
  • Pressing and releasing mouse really quickly within a single frame will probably crash.

I want to delete some data structures and just recompute them all the time.

permalink

* *
Jun 7, 2024
All the bugs in my software that others run into have the following story template:

  • t = n: my program was responsible for x and y
  • t = nn: my program became responsible for x, y and z
  • t = nnnnnn: bug in z (interacting with x/y)

Adding z required rethinking the entire program. I tried to patch it in. Implications were not fully worked out.

permalink

* *
Jun 5, 2024
Trying to build software for others is extremely disheartening. I can be eating my own dogfood on a daily basis for years and still newcomers hit bugs in their first 10 minutes.

I wonder if this is the major reason to huddle together on top of jenga stacks with tons of dependencies, terrified of fragmentation: You always need more testing than you think, and there's no way to compete with something that's been through that much testing.

I come stare at this abyss every year or two.

Doesn't do me any good, though.

(This post spawned a sprawling, constructive conversation.)

permalink

* *
May 4, 2024
Visualizing the digits of π

The following program lets you scrub the mouse downward to find more and more precise approximations of π within the red optical sight in the center of the screen.

permalink

* *
Apr 24, 2024
I've been idly looking at the space of possible rules for 1-D cellular automata.

To recap, you basically have a line of cells that can be in one of two states ('alive' or 'dead') and rules that decide how a cell's state evolves based on the state of its immediate neighbors to the left and right. The images below show a snapshot of time in a row of pixels, and time advancing from the top row of pixels to the bottom.

Starting from a single live cell, of the 256 rules 16 immediately wink out (empty grids in the picture below), 16 don't change (vertical lines), 48 move the cell (24 each to the left and right), 30 grow into triangles over time (6 each to the left and right and 18 on both), 18 form Sierpinski patterns and 22 are more chaotic. Here's a detail in Lua Carousel where you can see many of these types.

Detail of Lua Carousel browsing the space of possible rules for 1-D cellular automata, with each rule starting from a single live cell and rows further down showing its evolution over time.

However, things look different if you start from a random configuration of live and dead cells. Seemingly well-behaved rules hide subtleties, and seeming patterns vanish. Read more →

* *
Apr 19, 2024
The kids got a choose-your-adventure Oregon Trail book from the library, and I got nerdsniped into making a map for it.

(It's easy to get me to do something if it involves opening snap.love)

A tree summarizing a choose-your-adventure book. Each box has a page number, and you can visualize how the choices each page offers.

After finishing the map, I've been paying attention to the "meta game" of manually adjusting box positions and widths (height depends on amount of text) to make the arrangement pleasing to the eye. Constraints I've grown conscious of during this process:

  • Lining up child nodes vertically
  • Lining up nearby nodes. (imperfectly)
  • Avoiding long edges.
  • Keeping nearby edges approximately the same length.

I'd appreciate if anything seems jarring in this image, or if you have new OCD rules to infect me with :)

One frustration: I spent a while adjusting widths of boxes to not wrap lines within words, only to find that adjusting zoom messes things up again. This is an old problem: I can have precise scaling or crisp text, but not both. All my apps choose the latter.

permalink

* *
Apr 15, 2024
I'm reading a paper on my phone in bed and see this problem:

Convolving a list with itself. Given a list [x1, x2, ..., xn−1, xn], where n is unknown, construct [(x1, xn), (x2, xn−1), ..., (xn−1, x2), (xn, x1)] in n recursive calls.

And I am able to switch apps and solve it right on my phone, without needing to get out of bed.

I do have to put up with some klunky (Lua) syntax, though.

permalink

* *
archive
projects
writings
videos
subscribe
Mastodon
RSS (?)
twtxt (?)
Station (?)