Hi, my name is Heather Zeis and I program video games and the tools to make them. Maybe I could do that for you too!

You can reach me at heather.zeis@gmail.com.

Here's my resume. If you'd like to read some of my code, I've written a Breakout clone that's publicly available.

Below is more in-depth descriptions of some of my work in roughly reverse-chronological order. In particular you might find my long-running exploration of painterly rendering interesting.

Ready at Dawn

Though my time at Ready at Dawn was short (studio was shut down just 6 months after I joined), there was still a lot of opportunity to push their projects forward. The majority of my work focused on interfacing with artists to develop tools and workflows for the studio's first project in UE5.

Of most interest was a port of Unreal's Pivot Painter tool from 3DS Max to Maya. Pivot Painter is a tool for effeciently modeling and rendering complex motion on static meshes (for wind animation on foliage, for example). This was my first exposure to writing Maya plugins. It also involved writing a fairly complex material that deformed static meshes based on wind generated from gameplay events.

The Art of Flight

The Art of Flight is a dreamy shmup built by myself and friend Collin Eye with our proprietary Etcetera engine. Typically our work is split between him on design/gameplay programming and me for engine/tech work. For this game I completely rewrote our renderer to support variable backends (in this case D3D12 and Vulkan), implemented a Steamdeck port, developed a new build system for our engine (including automatic build uploads to Steam), and wrote a number of offline tools for pregenerating the "watercolor" backgrounds..

The Art of Flight is projected to ship in Q1 of 2025.

MTG Arena

In early 2021 I started work on the MTG Arena Rules Engine team. We're responsible for implementing large sets of new card designs for major releases roughly every two months. To facilitate this we use a natural language processing system to parse card text and automatically generate runtime code.

Luckily, Magic card rule text has a fairly strict grammar we call "Magickese" so the problem is tractable. We've implemented a set of NLTK syntax productions that produce parse trees with embedded semantics for each card ability. These parse trees are then transformed into CLIPS rule definitions with a custom Python compiler. Then at runtime a CLIPS interpreter/C++ harness executes a combination of hand-written rules and rules generated by the compiler.

Most card work involves extending the Python compiler to support new ability text but often need to modify other parts of the pipeline. The Rules team also handles localization, coordination with the Duel Scene (match UI) team, and providing feedback on early card designs to ensure they work well in a digital context.

Resident Evil 4 (VR)

Resident Evil 4 is a VR remake of one of the most beloved titles in the franchise. I started working on the game with Armature Studio in late 2019. The port involved a heavily modified version of the original game code wrapped in Unreal 4 for rendering and VR support.

My major contribution to the game was implementing a VR-capable UI library that was used extensively in the inventory/map/merchant screen/menus. The main advantage of the custom UI library is native support for multiple pointing devices. UE4 has a builtin UI system but it was designed with only a single mouse in mind, which poses some problems for a VR game. The library I built had support for many UI primitives including buttons, sliders, and scrollable regions.

One of the coolest parts of the original game was the inventory system. The player needed to manage all of their items in a size limited briefcase by translating and rotating them to cram everything in. This screen needed to be rebuilt entirely to work in a VR context and I handled the programmatic heavy lifting to get it done. The implementation involved the VR UI library mentioned above, UE4 Blueprints, and a bunch of wrapper code to tie UE4 and the original inventory management system together.

In the original version all of the inventory items are locked to a grid. The VR version allows you to pick up and rotate items to get a good look at them. Mapping an arbitrarily oriented object back into the coordinate space of the briefcase proved to be quite a challenge. The inventory also supports combining/attaching items, equipping weapons, and deleting items. (Note: the image below depicts the original game, not the VR remake.)

I also worked on similar overhauls to the Map and Files screens and developed a data-driven system for populating in-game menus.

Sports Scramble

Sports Scramble is a whimsical sports game that was a launch title for the Oculus Quest. This was my first project at Armature.

By the time I arrived there were only 5 months left on the project and most of the core gameplay challenges had been solved. There was quite a bit of physics fudging to allow for arbitrarily sized play-spaces and for synchronizing ball positions over a network. However, multiplayer only supported direct invitations to people in your Oculus Friends list.

My first major task was to implement a more robust system for joining multiplayer games, either through direct invites with friends or with randoms in a matchmaking queue. Originally players would abruptly teleport to the game once they connected but that was undesirable for many reasons. After connecting, two players would need to hang around in a lobby in order to select game options, and then smoothly transition to the gameplay level. This involved a complete overhaul of the game connection process and the way in which the levels were loaded.

The Oculus Quest had few other multiplayer games at launch, and as such we ran headfirst into a very hairy problem with the OS. At any time the player can back out of a game and into the OS menu. In order to maintain framerate while the menu is open, the main thread of the active game is suspended. Unreal handles all network data on the main thread, which means that suspending during a multiplayer game can result in dropping (so-called) "reliable" Remote Procedure Calls. A single dropped RPC in our game can have catastrophic effects and result in soft locks, so must be avoided at all costs.

In order to actually solve this problem, we needed a way to determine whether the remote player ever opened the OS menu during a multiplayer game, and immediately stop the game and reset to the last known stable state. The solution I devised turned out to be rather elegant: each player keeps track of an integer "generation", and the last known "generation" of the remote player. Each player periodicly sends both values over the network to the other player. If at any point the local player suspends the game or the remote player's generation is greater than the local generation, the game pauses and the local generation increments. Once the generations converge to the same value on both ends the game is reset and play resumes.

I also worked on a variety of other networking problems on Sports Scramble. This was the first multiplayer game the studio had made in Unreal, so there were some inevitable problems towards the end of the project. In many cases game state replicated incorrectly and soft locks were possible within some of the multiplayer menus. The asynchronous nature of a multiplayer game led to countless bugs that were nearly impossible to track down and replicate. The experience I had with From Other Suns was invaluable and crucial for launching Sports Scramble on time.

Fail Factory (Quest port)

After Sports Scramble wrapped, there was a bit of a lull in work while the next project ramped up. During that time I was tasked with (almost) single-handedly porting Armature's previous VR game, Fail Factory, to the Oculus Quest.

Fail Factory originally shipped on the Oculus Go, which had severe limitations. The headset itself was less powerful, there was only one controller, and both the headset and controller only had 3 DOF movement (rotation only, no positional tracking). The solutions required to work around these constraints were incompatible with the higher fidelity tracking of the Quest, so much of the core gameplay needed to be reimplemented.

For one, the player now had two controllers in their hands at all times. We decided to allow the player to switch hands by simply pressing a button on their off-hand at any time. This lead to many subtle bugs caused by assuming there would only ever be a single hand. For example, UI buttons would become stuck in a highlighted state if you swap hands while hovering over one. There were also more serious gameplay bugs in the same vein.

Another challenge was that the original version of Fail Factory was released with Unreal Engine 4.18 but shipping on the Oculus Quest required an upgrade to Unreal 4.23. The Go version made some simplifications to the 4.18 rendering pipeline to hit target performance. In the following versions of Unreal many changes were made to this pipeline, which broke our customizations. In order to maintain the same look these custom changes had to be forward-ported to Unreal 4.23, which required a significant deep dive into Unreal rendering internals.

There's always a lot that goes into any port project, and Fail Factory was no different. Including the above, I modified the achievement system to work with the new Oculus API, polished up the block-grabbing feel, and fixed bugs both old and new. Since launch it has been bundled with Sports Scramble for numerous promotional events and has helped drive sales of both titles.

From Other Suns

From Other Suns is a VR FTL-em-up, made with Unreal 4 at Gunfire Games. I joined Gunfire in the last year of the project, and worked on FoS until ship.

The most prominent feature I worked on was the player's inventory system. Players use the inventory by picking up weapons/items from the world and dropping them into a virtual bucket accessed from the other hand. While seemingly simple, FoS is a networked game so ensuring the state of the world/inventories was synchronized across all clients was a challenge. Due to the implementation of weapons and other items in FoS, it made sense to build most of the inventory interface with Blueprints.

Interstellar travel is at the core of FoS, and because the game is procedurally generated the universe needs to be different every time. I developed a method that computes star locations by maximally filling a volume with blue noise. The method was easily tunable and allowed us to change the density of stars and the volume of the universe. We tried a couple of universe shapes and interfaces for visualizing and interacting with the "Star Chart", including a large pannable stellarium, before we settled on a simple extruded capsule.

I also worked on weapon/item popups and ammo readouts, frequent networking/replication bugs, a bunch of support/bug fixes for the intro tutorial, a slew of savestate deserialization issues, and many smaller game feel features related to picking up, dropping, and interacting with objects in VR. All of the programmers on the team were generalists and we each had to have extensive knowledge of the entire codebase to diagnosis and fix issues with interconnected systems.

Etcetera

Etcetera is a custom game engine written by myself and my friend and close collaborator Collin Eye. When we first started making games together we worked in Unity but we didn't enjoy the workflow and wanted direct access to graphics APIs, which prompted us to develop our own engine from scratch. We're just a scrappy team of two (Etal Games) and an engine is a tall order, so the bulk of the work so far has been on infrastructure and editing tools.

We have shipped one game with Etcetera (Changes), with another on the way soon (Art of Flight).

The engine itself is written in C++, has D3D12 and Vulkan graphics backends, and runs on Windows, Linux, and (formerly) OS X. Since it's just two programmers working on the engine for the forseeable future, the focus is to improve our particular and opinionated workflows.

Since we don't have a dedicated level designer on our projects we needed a flexible tool that allowed us to work quickly. We implemented a Constructive Solid Geometry (CSG) system that allows us to quickly sketch out indoor scenes. Our tool supports Union, Difference, and Intersection operations, arbitrary nesting of primitives, and outputs watertight meshes.

Once the main algorithm was working, the biggest challenge with the CSG system was building a set of tools that integrate well with our scene editor and were usable at interactive rates. We spent a lot of time architecting an immediate mode UI library that we have used for all sorts of internal tooling and in-game UI.

The focus on the CSG tool thus far has been on indoor scenes but we have plans to extend the tool to support terrain and roads.

One of the most useful features I've built for the engine is a preprocessor that generates a bunch of runtime-inspectable introspection data. For instance, we can mark up a struct in the code with STRUCT() (similar to Unreal's USTRUCT()) to generate a Schema that stores the name, byte offset, and types of all the fields. This data makes it easy to serialize data, write GUI editors, generate lists of all assets referenced in a scene, etc.

The preprocessor also generates a ton of boilerplate code that helps us avoid mistakes and work a lot faster. For instance, we can mark up any struct as an ASSET() and automatically generate an entry in the AssetType enum, a handle type so we can store a typesafe weak pointer to a loaded asset, and overloaded functions for loading assets of that type. We do a similar process for storing weak "persistent" functions that can safely survive after reloading game code at runtime. Essentially, our preprocessor acts as a more powerful standin for the macros we were used to from our experiments with LISP and Clojure (see below), and does more to increase our productivity than any other feature in the engine.

There's a ton more in the engine that I haven't mentioned yet: an audio mixer, scene/script editing tools, asset/level streaming, asset/code hot-reloading, prefabs, a 3D physics engine, and more! While there's so, so much left to do on the engine, we now have enough groundwork laid to turn an eye towards gameplay implementation.

Painterly Rendering

One of the biggest draws to creating our own engine is the draw of a unique art style: painterly rendering. Others have attempted it in the past, but there are few examples of real-time, 3D painterly rendering that meets our quality requirements. From day one with the engine (in late 2015), one of our goals has been to develop this style; it's been a long time in the making!

Our first iterations of the paint rendering used a deterministic post-process effect that rendered strokes along approximate isolines in the scene. Unfortunately, this image-based method led to a lot of flickering and frame incoherence. The computed isolines were lighting and view dependent, and changes to either would result in large changes in the output.

The paint particles themselves were alpha-blended in an RYB colorspace (red, yellow, blue) instead of RGB to better simulate paint blending. For example, in the RYB space an equal mix of yellow and blue will result in green (ryb(0, 0.5, 0.5)) instead of middle gray (rgb(0.5, 0.5, 0.5)).

Next we tried a fairly complex algorithm that attempted to dynamically fill projected splines in real time with square "splat" textures but we found that it was fairly inflexible, a performance hog, and didn't resolve the flickering and frame incoherence problem.

The next approach was more geometry-based than image-based. We built a set of stroke primitives in 3D space and rendered those directly with alpha blending. This solved the stability issues, but came at the cost of a lot of artist-effort and/or a complicated process to produce geometry offline.

The current, and most stable, approach is a bit of a hybrid between the image-based and geometry-based approaches. Strokes are generated in screen-space using output from a standard deferred renderer. To maintain stability the strokes persist across frames. To enable this each stroke point also stores world-space position and reprojects using the new frame's viewProjection. Then strokes are evicted based on a number of factors and new strokes are generated to fill any gaps.

Changes

Changes is a small puzzle game based on Conway's Game of Life. With the simple addition of a rule that allows a player to push around cells prior to each generation much of the inherit chaos in the Game of Life can be controlled. We also discovered that it's possible to transform any of the basic still lifes to any of the others (block to boat, boat to tub, etc.) and moving these stable structures around can be accomplished with care.

In summer of 2018, Thekla announced a grant to support independent puzzle game developers. We had the idea for Changes floating around, so we developed a prototype to submit. We built the game using the Day of Days engine as a way to stress test on user machines. We only had about a month to work on the project, so I handled most of the programming tasks while my partner focused on puzzle design.

At the time of the grant application submission we had designed 150 puzzles and I had built a robust input queue, animation system, full undo support, and a first pass on a bioluminescent "goop" renderer.

While a month is not a long time to work on the project, we still made time for a round of external testing. Our main focus with the testing was feedback on puzzle design and to see how well we were teaching players how to solve puzzles. Since we wouldn't be able to watch over our testers' shoulders, I built a system that logged all player input so we could play it back and see exactly where people were having trouble. This was a great help overall and our testers found many solutions that we would have otherwise missed.

Since the initial grant proposal, my partner has continued to work on the game. On occassion I have helped out with graphics related tasks, including a recent big overhaul for a showcase submission. Below is a screenshot of an early build.

Changes is available for download on Steam!

Breakout

Breakout. A true classic. During a short break from other projects I built this clone as an exercise in polishing my modest programmer art as far as is reasonable. My focus was bold randomized palettes, simple-but-juicy animations, and solid game feel that's always smooth.

You can download the binaries, or, if you're adventurous, build from source (requires Visual Studio 2017).

Multiversi

Multiversi is a 3-player strategy board game that fits in your pocket! It used Apple's GameCenter API for client communication and supported local hotseat games. It was on the App Store circa 2013 (but it's no longer available. Sorry!).

The game plays similarly to Othello/Reversi but with 3 players and a hexagonal grid in various configurations. Players take turns capturing spaces until none remain. The player with the most captured "target spaces" wins.

Jungle Racer

Jungle Racer is a 4-player keep-away game set in the Tron backwoods. The game is written in Clojure, built on top of libGDX. It was an attempt at writing games in a "Clojurey" style using a messaging system built on core.async and transducers.

There are more reasons why it was a bad idea than can fit on this page. Suffice to say maintaining interactive framerates was problematic and immutable data does not always lead to clear gameplay code. While the technology behind Jungle Racer was quite suspect, I developed several other game prototypes using the same libraries and found that sometimes learning what not to do is an acceptable approximation of learning good practices.

(doom)

A classmate once told me that "nothing useful can be done with LISP." Taking that as a challenge, I started immediately producing something useful: visualizations of all the levels in id's Doom, ripped straight out of the .WAD file.

The Unofficial Doom Specs (version 1.666) were quite helpful, and I was pleasantly surprised to find the file formats were so well documented by modders.

High Noon

A game made in 48 hours for Ludum Dare 29 (theme: "Beneath the Surface"). Written in Clojure. You can play it, if you want.