Rounded Realism

Having cut my 3D teeth on Hash Animation:Master, I like working with subdivision surfaces, until it’s time for a high-to-low poly sculpt for normal map baking. It’s always interesting how clothing only looks “right” when it’s modeled after a plausible sewing pattern.

Minus a few things out of my control, it looks like Lillie is the Keeper is only a week or two from release.

Without a AAA-scale production pipeline, an appropriate art style needed to be developed to maximize impact while easing development–especially since I’d be modelling everything myself. Low-polygon games like Black Book and Röki have had great artistic success, while voxel-style titles like Minecraft and Roblox have enjoyed great commercial success, both styles riding the “high-tech low-tech” aesthetic of my old professor. Having observed that modern iPhones (even in web browsers, via WebGL) can easily draw a very large number of polygons, I decided to lean instead into something I call “rounded realism.” This style builds on the work of artists like Aron Weisenfeld, Zinaida Serebriakova and Chris Van Allsburg, in which figures and objects are realistically textured and atmospherically lit, but conform subtly toward primitive solids.

In practice, rounded realism means that textures are realistic (photographic, when possible, utilizing Adobe Capture) and lighting is clean and realistic, but unimportant details are missing. Corners are bevelled, with sizable flat surfaces between. Visual outlines are clean and geometric, with a minimum of visual clutter. Faked volumetric lighting and other transparent elements are used extensively to create depth, running against the orthodoxy that their layered overdraw will kill iOS performance.

Clothing was some of the most demanding work. Mayme’s Edwardian sailor bodice outfit closely follows a custom build by Katja Kuitunen, based on a vintage piece from the era. It’s constructed from Kuitunen’s sewing references–with help from my girlfriend, who is brilliant with these things. The skirt (a separate piece, despite the shirt’s matching fabric) is a simple fabric tube, gathered about the waist, with realtime cloth simulation. Everything is designed to be plausible, but clean, geometric, and simplified.

It’s been a big job, and there’s more I’d like to do for a 1.1 release. The only models from the game that aren’t my own are human bodies built with MakeHuman, though even they’ve been resculpted and touched-up. Clothing and hair are all hand modeled.

It Was His Time to Go

A fragment of a dream. This was the new normal. Everyone seemed so resigned to it. Maybe it was indeed the time appointed to the man, somehow, as someone said afterward, but he was panicking as they lifted him out the window. Arms came out of the ocean–long, brightly colored arms, with sticky, webbed hands–amphibian hands–pulling the terrified man right off the subway car. His friends tried desperately to hold him, but there was no resisting. He was already suffocating, head wrapped in those rubbery fins, as he disappeared beneath the leaden waves.

I wonder what the arms were connected to. Why did we stay near the ocean? Was it futile to leave? How long had we been living like this? How many of us were left?

A Little About Role-Playing Games

Lillie is the Keeper is a role-playing game, but it’s not an RPG. Like many games, you play as the title character, and see the world through her eyes (and hear yourself speak in her voice, and hear the voice in her head). Minus the AR gimmick, it’s a very conventional style of play. What I find odd, on reflection, is that this type of game is not called a “Role Playing Game”–and what is involves very little role-playing.

It’s a product of evolution, like all inexplicable things. Pen-and-paper RPGs were supposed to model all aspects of playing a character, though in practice most players focused on stats as a means to an end: that of increasing said stats. People “gamed” role-playing games, rather than role-playing them.

Sure, some people didn’t (and don’t) play them that way, and there are a number of role-playing systems that focus very sharply on driving character interaction rather than combat (somewhat including my own) but when the nascent digital game developers needed a template for long, multicharacter games, they borrowed liberally from the established pen-and-paper RPGs.

The real game loop is: boost numbers so you can unlock more content and boost more numbers. Even incredibly good story-driven RPGs like Lunar: Silver Star Story are fundamentally stat boosters with unspooling scenery. Very little of your time is spent inhabiting the mind and game world of the title character. Most often, the title character’s mindset is as bland as possible, to serve as a passthrough for the gamer. (Successfully un-personifying “you” as the main character was one of the many unsung triumphs of Myst.)

I call Lillie is the Keeper a “Myst-like.” It’s an out-and-out brag posing as a point of reference, even if it’s not 100% true. Myst has been misunderstood for 30 years, because its imitators, from Obsidian to The Witness, have failed to understand the importance of aligned atmosphere, the value of stillness, and the fact that walking around an empty place solving puzzles isn’t any more compelling than doing either activity solo—hence puzzle games and “walking simulators.” In Myst, the “puzzles” are just broken machines of a perfectly recognizable vintage. Within the calm loneliness of the game’s sound, pacing and art, there’s not even a sense that they need to be fixed (at least until the puzzling discovery of the imprisoned brothers). The opening act of the game makes clear that this is a game for a certain type of player: Not a twitch gamer, nor even a problem-solver, but a wanderer. A dreamer.

LitK tries to be this. It tries to be a positive depiction of an individual with a mental health issue. It tries to be an innovative new use for AR. It tries to be an interesting short story. It also tries to be a role-playing game.

Do we need a new term? How about “character-playing game?”

A lot of what I enjoy about games is the escapism: losing myself in another place, another time, another me. I can’t be the only one, can I?

Herre Solsort

Printable PDF

An illustrated true story about our local blackbird. This was something I started over the summer as a 2D/digital painting exercise, but only picked up again a few weeks ago (as something to do that’s not job hunting and Lillie is the Keeper).

I love the self-serious, slightly derpey way blackbirds go about everything. Lots of fun to scribble something together like this. Have fun!

Introducing Lillie is the Keeper

You’ve always wanted a lighthouse. A contained, liminal space at the edge of the world, between sea and sky. A literal beacon in the night. I can put one in your pocket.

Lillie is the Keeper (LitK) is an upcoming Myst-like game played in AR on an iPhone or iPad. You’ll take on the role of Lillie Pine, a 15 year old girl shipwrecked in an empty lighthouse following a mysterious calamity in 1905. With your iOS device, you’ll explore the lighthouse by moving about your real-world space.

The game is structured as a short story, beginning before it begins and ending after it ends, in eight play vignettes or levels. The main character, Lillie, suffers from intrusive thoughts, which we hear as an older, more severe, highly critical version of her own voice.

The game is built in Unity 2021.2 and runs in iOS 14, using AR Foundation 4.2 as a wrapper over AR Kit 5. It is a solo effort by the author, including writing, voice direction, modeling, coding, rigging, sound editing and visual design, and stars actors Sheri Lee (Hearts of New England), Robert Harrison (The Equalizer) and Melissa McCue-McGrath (BewilderBeasts).

Tiling 3D Noise in Blender

My game “Lillie is the Keeper” needed a small-scale ripple texture for an ocean shader. The in-game shader makes use of a 3d texture, UV sampled in world space horizontally, with the sampler moving up through the texture’s z axis over time to animate it. A first version used the old 4d rotation trick for repeating noise, in which a 4-dimensional noise texture is rotated 360 degrees around the Lovecraftian W axis between UVs 0 and 1. It tiled horizontally, but when the 3d texture repeated (up the z axis) there was an ugly little crossfade between unrelated frames.

I use Bforartists, a UI-focused fork of Blender, for 3d graphics and some texture creation–like this project. It doesn’t fix every pain point, but I can’t recommend it highly enough. This method, and the attached .blend file, will work just the same in mainline Blender.

As there’s no 5d noise function in the shader nodes (Shading tab), for the improved ripple texture we must go back to fundamentals. Ken Perlin’s original version of a solid texture–Perlin Noise–has a lot of complicated math behind it, but geometrically is pretty straightforward: Create a 3d grid, place a point at a pseudorandom location within each box, and apply a function to smoothly interpolate between the points in all three dimensions. (As best I understand it, Perlin’s own improvement, Simplex Noise, replaces the grid with tetrahedrons–triangular pyramids–but that’s at least Whisperer-in-Darkness-grade math for me.)

There is a shader node that can perform similar interpolation: Point Density. Note that you’ll have to switch your renderer to Cycles in order to use it. In Eevee it’ll just output black. This is poorly documented and the interface won’t help you.

The Point Density node takes the vertices of a mesh (or particles of a particle system) and outputs a greyscale representation of their density. With it, it’s possible to create noise from your own handmade 3d grid of points–like an array of cubes. Since you’re creating the vertices yourself, making them repeat is as simple as replicating them in x, y and z–for instance, with three Array modifiers.

To start out, create a 1x1x1 cube. Pop into Edit Mode and set the cube’s origin to its leftmost, bottom-most, hindmost vertex. Back in Object Mode, move it to -1.5, -1.5, -1.5. Just one cube (8 points) won’t look like much as noise, so we’ll double it in all three dimensions. Scale your cube to 0.5, 0.5, 0.5. Now double it in X, Y and Z with three Array modifiers: Add an Array modifier, set the Count to 2, and the Relative Offset’s Factor X to 2. Add two more Array modifiers, for the Y and Z axes (Relative Offset Factor Y to 2 on the second, and Z to 2 on the third).

We can randomize our cubes’ vertex positions with another modifier: Displacement deforms the mesh based on a texture. The app can generate this noise texture for you. Go to Texture Properties, create a new Texture, set the type to “Clouds,” and select “Color” rather than “Greyscale.” Go back to your cubes’ modifiers, add a Displacement modifier (after the three Array modifiers), set the Coordinates to “Global,” the Direction to “RGB to XYZ” and the space to “Local.” Play with the Strength and Midlevel properties if you want more distortion in your cubes.

A cube of (distorted) cubes

Now you’ve got a box of eight distorted cubes, sort of down in the lower left-hand corner of the world axis. Let’s replicate them with three further Array modifiers: After your Displacement modifier, add an Array, and set the Count to 3. Since the Displacement modifiers are messing with the overall dimensions of the cubes, deselect Relative Offset and select Constant Offset. Set Distance X to 2. Now, make two more Array modifiers, for Y and Z, also with Constant Offset Distance 2 (in Y and Z respectively). You should now have a repeating set of distorted cubes in all 3 dimensions.

Hide your cubes (including from renders–the camera icon in the outliner). Create a 1×1 plane at the origin. Delete any lights in your scene. Set the camera’s output to a texture-friendly square, like 512×512 pixels (printer icon, Resolution). Set the camera to Orthographic (camera icon, Lens: Type) and aim it straight on to your plane. Create a new Material on the plane (material ball icon, New).

Switch to the Shading tab with your new Material, delete the “Principled BSDF” node, and instead add a “Point Density” node. Select “Object Vertices” rather than “Particle System.” Under Object, select your mesh of repeating distorted cubes. Set the Space to “World Space,” the Radius to 0.5, and the Interpolation to “Cubic.” Drag the node’s Density output straight to the Material Output node’s Surface input.

Shader nodes

That’s it, in a nutshell. Move your plane between -0.5 and 0.5 Z, and the noise pattern will repeat. It’ll also wrap around at the X and Y edges.

You can create finer-grained noise by doubling your cubes and halving their scale. Or double-doubling and half-halving. Or double-double-doubling… You get the idea. For each iteration, half the scale, double the Count of your first 3 modifiers, then double the distance between them with your last 3 modifiers. I also recommend halving the Size attribute of your Clouds texture each time.

Note that at larger numbers of cubes (say 16 or 32 per box) the “Point Density” node’s Resolution attribute will need to be increased. Add 100 at a time, until there’s no visible difference adding 100 more. Accept the hit in performance. (If you see seams in the final rendered texture, too low a Resolution setting here is usually the culprit.)

Download the Bforartists/Blender file here: Repeated Tiled Noise v2.blend

In the file, the Demo collection will demonstrate the simple version, while the Production collection is my final water ripples setup. In the simple version, there are also some unused shader nodes demonstrating a setup for combining noise at different scales to create more complex output–again, just like Perlin Noise.

The Production collection, my own version for water ripples, does a few additional things. I’m creating the 3d texture in Unity, which requires each frame (vertical slice of the 3d noise) to be stacked side-by-side in a single image file. As such, I’ve added an Array modifier to the plane I’m rendering, so that it becomes 16 side-by-side squares stepping up between -0.5 and (almost) 0.5. (Almost 0.5, because step 17 would be 0.5–and we don’t want to repeat a frame. The app will do basic math when setting fields numerically, so entering “1/16” will give you 0.0625…) The orthographic camera is adjusted to render it all in one frame. Within the shader graph, I’ve made the position Vector fed into the “Point Density” node a combination of the world Z coordinate and the planes’ UV coordinates (standing in for X and Y). Since I want a lot less change as the noise loops in the Z axis than in the horizontal directions, I’ve not halved the number and scale of the cubes along the Z axis, and I’ve split the Displacement modifiers’ textures into three different Greyscale textures accordingly. There’s an RGB Curves node added after the Point Density node to make the interpolation more wave-like. Finally, the greyscale heights have been translated into a Normal Map via a Bump node.

Smooth sailing!

Admiral Bulletin & the Internet Elucidation

In a blog dating to 1999, not everything makes it over from one engine to another. Back in 2005, I wrote a four-part piece of literary criticism fiction on the history of the Admiral Bulletin franchise. It’s actually kind of good.

In celebration of resurrecting the Admiral Bulletin storytelling game, I’ve added it back to the WordPress blog with tags and correct dates. You can find the four parts (and notes) here:

Admiral Bulletin 2.0

Admiral Bulletin is a storytelling game. Players choose locations and follow prompts on Event Cards to collaboratively tell a ’30s pulp adventure story. Players can reward one another for especially entertaining additions. The game is 100% free and can be printed at home, as well as supporting online play over video chat.


2+ (ages 12 and up)

You’ll Need:

  • 16 Character Cards (Gold)
  • 32 Event Cards (Blue)
  • 3 Chapter Sheets (Acts I, II & III)
  • A handful of counters (glass beads, coins, etc.)
  • A pen
  • A bit of scrap paper, or your phones

Setting Up:

Give everyone 3 counters, and leave the rest in the center. Put out the Act I Chapter Sheet.

If there are Characters people don’t want to use in this story, remove them from the deck. Pull the Character Cards for Bulletin and Miranda out, as they’ll be required in Chapter 2. Shuffle the rest of the Character Card Deck.

If there are Events people don’t want to use, remove them from the deck. Shuffle the Event Card deck.

Everyone now writes down 3 Locations:

  • 1 specific (Paris, Asia, Tiffany’s, etc.)
  • 1 vague (at sea, on a train, etc.)
  • 1 more of the player’s choice

The starting player is the person who most recently finished a novel.

Beginning a Chapter:

Unless the Chapter already has a Location filled in, the starting player chooses one of their Locations, adds it to the space on the Chapter Sheet, and announces it to the other players. Locations can be used more than once.

If the Chapter has one or more empty Event Card slots, draw cards from the Event Card deck to fill the slot(s). Many Chapters come with Event “Cards” of their own. If Event Cards say to add Characters, draw the appropriate Character Cards and add them to the Character Pool before starting the Chapter. Otherwise, the Event Cards will guide your storytelling during the telling of the Chapter.

When ready, the starting player slides their first counter forward and begins speaking. Until the end of the Chapter, the only spoken words should be story content.


When it’s your turn, you push the first of your 3 counters forward, and begin speaking. Each counter represents one natural, speaking breath (roughly a sentence). You speak all 3 breaths, and then take your 3 counters back as the next player begins.

If playing over video chat, you can hold the counters in your hand, or just use your fingers as “counters.”

The Character Pool reminds you of the recurring characters in the story, all of which are available for use. One-off characters and henchmen are yours to invent as you go, but shouldn’t persist beyond the current Chapter.

1 Up:

If you particularly like what someone adds, you can reward them with an additional counter (from the center). They can use this on their next round, though it doesn’t come back after they’ve spent it like the original 3 counters do.

On video chat, type the person’s initial into the chat window to give them a 1 Up.

Ending a Chapter:

When the Chapter reaches a good stopping point, push all three of your counters into the center without speaking. The Chapter ends.

The next player now becomes the starting player. They select a Location, draw cards as needed, and begin.

Keep in Mind:

  • Have fun! It’s not a competition.
  • Let the story decide where to go.
  • Don’t race to the end.
  • This is improv: “Yes and…” one another.
  • Use the cast.
  • When in doubt, character quips.
  • You’re doing better than you think.
  • Have fun!


Admiral Bulletin v2 beta 1

A Christmas Ghost Story

What do you do the night of Christmas Day? When all the presents have been unwrapped, the food eaten and the visits made? There's an old tradition, predating M.R. James and Charles Dickens, and even the author of Gawain and the Green Knight. I think we should bring it back: The telling of ghost stories.

Cantwell had never taken the time. Another version of her would have assumed that she knew what a ghost was. The present Cantwell was rarely the type to bother with abstractions. What a ghost was cost her no more concern than the question of what a friend was. Were either real? Her friends demonstrated fealty on the right apps and were present in person when circumstances required. This sat comfortably enough in place of a definition. Likewise were ghosts considered by some influential people (and what other kind existed–meaningfully existed?) to be a thing one could accept as “real.” Our alternate Cantwell would have said that a ghost was what remained when a person had otherwise died. They symbolized the inevitable loss of beauty and influence that preceeded the grave by so many years (for those who couldn’t contrive to go out on top) but were otherwise nothing more and much less than a person on this side of the ground. The thought of meeting a ghost hadn’t crossed Cantwell’s mind since she had been very, very small, and understood very, very little.

This Cantwell, the present Cantwell, got by with surface glosses in place of understanding. Understanding was a thing that lived in a stillness she simply didn’t inhabit. She felt naked without a constant crush of attention from all sides, like some deep sea chamber that would rupture if brought to the surface.

It came to pass, however, that she found herself in just such an unaccustomed stillness passing the canal opposite Christiansborg. Her devices were as silent as the unseen water below. Given their use during the day’s brief sunlit hours, this was not mysterious, though car headlights somewhere in view would have been more usual. The silence ate at her much more than the darkness and the cold. Thoughts echoed that didn’t feel like hers.

She was not precisely in her right mind, if one can ever be said to be. An unsettled mind is usually crosscrossed between past conditionals and possible futures, in Cantwell’s case none more than 48 hours in either direction, but hers was also occupied with several alternate presents where others had granted or withheld one thing or another.

Cantwell had a place and time to be, and was hating it as much as the remainder of the present void. The city could be any city of sufficient cachet to her; she didn’t speak the language and didn’t care to, for they spoke hers. Places were backdrops, set dressing. The bare black stage around her was growing intolerable. It was, in fact, the longest night of the year.

There was another, opposite her. A ghost.

Cantwell noticed her, and had the unacustomed jolt that noticing her was her first and most fatal possible mistake. In the way that one knows a greyness under a lamp is a human shape, and that a blank oval near its top is a face looking at us, Cantwell saw it out of the corner of her eye. She pretended, unconvincingly, that she hadn’t. Normally, she pretended so effortlessly and so totally that she herself believed it. Truth was, to Cantwell, what others would follow, and the strongest opening move in affecting a truth was to believe it herself. There were fallbacks, of course, on the vanishingly rare occasion of being trapped in a “lie:” crying, screaming, inversion… But belief could only take one so far. Her skills were deserting her, and would not save her.

Across the canal, behind the low railings, the figure matched Cantwell’s pace. There was a sound of footsteps on stone. She knew it was a woman, as she knew it was a ghost. The figure followed. The canal turned, and the figure didn’t. It forded the air at a calm walking pace, at an angle to meet Cantwell’s path. The silence echoed more loudly than the noise. Had it passed through the railing? Apparently. Even looking wouldn’t tell her, and Cantwell was absolutely not going to look. In another context, it could have been a school friend or a colleage from some job quickening step from across a street to trade commonplace words. Here, however, nothing could be commonplace. The grey-black mass took up more and more of Cantwell’s peripheral vision. A second set of footsteps began on the cobbles to her right, matching rhythm. The ghost walked along beside her.

“Aren’t you going to-“

“No!” Cantwell snapped, equally surprised to hear her own voice.

“You can’t know how much I hate you,” remarked the ghost, also not making eye contact. Cantwell hustled on, saying nothing. People, other people, would save her. Her silence, far from rallying strength like usual (stillness could also be used offensively as a weapon) resulted only in a gently lengthening sense that she was making herself an object of pity, drawing out the inevitable.

“I don’t need this from you,” said Cantwell, eyes set straight ahead. Her piteousness rose to something like self flaggelation.

“Tonight’s not about what you want. Tonight is about what I want.”

What was this? Was she going to be hurled–hurl herself, but not really–into the canal? Float to be found at late morning light an ugly corpse? Self-killed (so it would appear) without a mark of respectable violence?

“No,” said the ghost.

“What do you want?”

Nothing happened. She wasn’t transported, or overwhelmed with a sudden hallucination. To be truly overwhelmed with something is a rare gift in life, and this was not the night for gifts. Cantwell was no less aware of the cold air up her skirt or the trouble of negotiating each increasingly slick paving stone in her high boots. It was as if a smell from long ago triggered a sudden memory. Cantwell’s emotions were once again in a tiny room overlooking another city. Nana was baking macaroons. Some were red, some were white, and some were yellow. A bowl of blue batter remained. She had done nothing to help, just sat at the table kicking her feet and eating. It didn’t matter. The halo of something was in the air. Little her didn’t understand why everything was good, and didn’t care. Why would a child?

“Shut up!” yelled the ghost. Cantwell was startled, and almost looked over. There was no one else there, but the ghost didn’t seem to be addressing her.

She saw a window. A small, far off, lighted window, on a third floor, looking warm as the finished wood inside, all of it seeming to glow. Was this the only lighted window in view? The last one in the world? “I don’t understand,” she began, but was cut off again.

“Never! Ikke nu, ikke hver,” continued the ghost, to whom- or to whatever. Cantwell seemed to be momentarily forgotten.

“You’re not real. It isn’t real.”  Tears pricked at Cantwell’s eyes. They served no purpose. They weren’t going to move the ghost. There was no one else to help–that much was increasingly clear. Cantwell wanted to control them, but with a dropping feeling found that she absolutely couldn’t.

She still hadn’t looked at the ghost. She wouldn’t. It was the only fight she hadn’t lost. The figure seemed in appearance about her age. A woman. Dressed in something colorless, perhaps warmer, or maybe older. It could have been her doppelganger. It could have been anyone else.

“Wouldn’t you like to know where I’ve been, before I was here?” They walked on in silence for a moment. “I wasn’t an ugly corpse. I know how that matters to you.”

Cantwell couldn’t form the words, but the ghost did for her:

“What do I know about you? No, this isn’t your night for questions.” It scratched its nose. “Not even the rhetorical kind. You tell me: Why were you at your Nana’s?”

“Mom was ripping the apartment up.”


“She was all cut up about some man cheating on her.”

“Her moods are extreme.”

“He wasn’t even my father. It’s not like I cared.”

“I’m taking that away from you.”

“That man? I barely even remember him.” Cantwell stopped herself. It wasn’t that memory the ghost was taking. It wasn’t any memory the ghost was taking. Worst of all, it wasn’t her life either.

“What am I?” the ghost asked.

“You’re a ghost. You’re just a ghost.”

“And what is a ghost?”

“Don’t do this.”

“I thought you didn’t try to understand things. Just the surface, remember? Stay in the flow. It’s the silence that scares you. I’ll bet right about now you’re wishing you were stupider. There are things you don’t understand, but then there are things you can’t understand. That’s what I am. That’s what your ghost is.”

“Please!” Cantwell looked, but there was nothing there. “Please!” The memory was just as fresh. The moments in that kitchen. True to her word, the ghost hadn’t taken the memory. Cantwell could remember every detail with painful accuracy. Only the feeling was gone.

At some point it had begun to snow. Cantwell continued on to her appointment, in that wooden room on the third floor.