All work Released desktop product · live browser game

Pixal & ASTRA BLACK

I designed and built Pixal, a desktop studio for local AI image, video and sound. Then I used it to art-direct ASTRA BLACK, a creature-card game you can play in the browser today. Forge, the pipeline between the two, is in development.

Role
Pixal: solo design + engineering · ASTRA BLACK: creative director
When
2026
Built with
ReactPython / aiohttpComfyUIthree.jsKrea 2 · Qwen Image 2.1Qwen3-TTS · Stable Audio 3Claude + Codex agentsPlaywright WebKit
01EnginePixal 02FactoryForge 03GameASTRA BLACK

Explore20 renders, three style directions

ChooseKrea 2 through Pixal, seed 9100

Matte + gateChecked for alpha haze and a face

PrintThe game’s renderer, 1024 × 1430

Fig. 1Gloamlynx, one of 88 creatures, through the stack. Every frame is the real file from that stage.
The problem

Open image, video and audio models are now good enough to make a whole game’s art. Running them locally still means wiring node graphs, matching model files and troubleshooting setup. And a game needs hundreds of assets that belong to the same world, not one lucky image.

My role

Pixal is mine end to end: product design, interface and implementation. On ASTRA BLACK I was creative director and the only human on the team. I set the premise and the art rules, played every build and gave the go on every release. Claude and Codex agents wrote the game’s code, and its creatures, characters, voices and music came out of Pixal.

The outcome

Pixal 2.1.1b is public, with a Windows installer and a product walkthrough at getpixal.com. ASTRA BLACK is playable at astra-black.com, shipped as 22 receipted releases between September 20 and 24. No adoption numbers yet: playtesting so far is me and one friend.

88creatures across two card sets, each with a battle kit
206voice files, generated locally through Pixal
22receipted releases in five days, any one reversible
442images through the art gate, zero failures on the last pass

01Engine

Pixal

A desktop studio for local AI. Describe the work in a conversation, then edit it, animate it or give it sound, without opening a node graph.

Status
Released
Version
2.1.1b · September 2026
My part
Product design, interface, implementation
Explore Pixal
From the public Pixal walkthrough, built from the product’s interface source. A staged demonstration; the image is an existing Pixal / Krea 2 render.

1.1The interaction model

The conversation is the front door.

ComfyUI is powerful because you control the graph. Pixal keeps that power and changes where you start: describe the work, choose a model and a style, attach a reference and set the canvas.

The technical choices live in named recipes. A compatible recipe supplies the encoder, sampler, scheduler and the rest of the graph, and the result stays inspectable. Its model, seed and size travel with the image.

The tradeoff: a recipe constrains the starting point. In return the same request takes the same route every time, and anyone who wants more control can open the recipe and its LoRA chain.

  1. 01

    Your intent

    Prompt · model · style · reference · canvas

    Creative choices
  2. 02

    A compatible recipe

    Encoder · sampler · scheduler · graph

    Inspectable defaults
  3. 03

    A result you can work on

    Model · seed · size · edit · animate

    Next actions stay with the work

1.2Correction is part of creation

“Change her hair to platinum blonde.”

A useful result usually needs a correction. Pixal puts Edit on the result card and hands the existing image to an image-edit model, and the request and its result stay in the conversation.

These are the two published outputs from Pixal’s walkthrough. Switch between them: the pose and setting hold, and the edit also changes a few smaller details.

The tradeoff: an instruction edit can alter more than the feature you asked about. Pixal also takes a painted mask for a tighter edit, which gives you a way back when a whole-frame edit goes too far.

Original engine
Pixal / Krea 2
Edit engine in this example
Pixal / Qwen Image Edit 2511
Evidence
Existing published renders, shown uncropped
Compare the published outputs
Original Pixal render: a dark-haired ninja kneels beneath a cherry blossom tree.
Original · Krea 2
Edited Pixal render: the same composition with platinum-blonde hair and small changes to other details.
Instruction edit · Qwen Image Edit 2511

1.3Setup is part of the product

Design for the machine you didn’t build it on.

The installer inspects what’s already on the machine. Models show as ready, in the wrong folder or missing; downloads resume after an interruption, and reopening setup takes you back to your progress.

Hardware limits belong in the interface too. Pixal detects a VRAM tier and explains what each engine needs. Generation runs through ComfyUI on your own machine; the chat model can be local or remote.

The tradeoff: local generation moves cost and setup onto your hardware. Windows is the supported install path, models take real disk space, and an optional remote chat provider can still charge for usage.

02Factory

Forge

The pipeline between the engine and the game. It drives Pixal in batches and turns raw renders into assets a game can load.

Status
In development
Started
September 2026
Produced so far
ASTRA BLACK’s art and audio

Private while in development

Forge’s matte check, 18 September. Every cut-out goes onto dark, light and orange before it reaches a card, so a fringe or a halo has nowhere to hide.

2.1Why it exists

A game needs a whole set that belongs together.

ASTRA BLACK needed creature art, cut-outs, backdrops, expressions, voice lines, music and effects from the same world. Making them one at a time in a UI would never hold that together, so Forge talks to Pixal’s local API instead: stills in batches, clips one at a time, audio through its own routes.

Before Forge, 24 separate scripts each carried their own path into Pixal. Now there’s one contract. The engine is read-only, the factory owns the pipeline, and a game only ever loads a finished theme. That boundary is what lets the next game reuse the pipeline without touching the engine.

  1. Engine · Pixal

    Renders stills, video and audio. Nothing above it edits it.

  2. Factory · Forge

    Batches the renders, keeps the raw frame, mattes, gates and packs a theme.

  3. Game · ASTRA BLACK

    Loads the theme: creatures, characters, backdrops, title, music, effects and voice.

2.2The creature pipeline

How a creature gets into the game.

  1. 01

    Render

    Pixal runs Krea 2 with style LoRAs, on a flat #808080 plate so glossy parts keep their true colour.

  2. 02

    Pick

    An agent shortlists the best of each batch. I overrule it when I disagree.

  3. 03

    Matte

    MatAnyone2 keys the cut-out with anti-aliased edges, and glow survives as partial alpha.

  4. 04

    Gate

    A script fails any alpha haze and any creature without a face point. Last release pass: 442 images, 0 failures.

  5. 05

    Print

    The game’s own canvas renderer prints the card at 1024 × 1430, then three.js lights it in 3D.

2.3Rules in code

Protect the art direction at the source.

forge/client.py
def verbatim(name: str, prompt: str, **opts) -> tuple[str, str, dict]:    """One shot with the brain switched off, the only correct mode for art.     Returns the `(name, text, opts)` tuple `run_batch` expects. `name` is how    the asset comes back in `render`'s result and in `on_done`, so make it the    name you want to stage under.    """    return (name, prompt, {"prompt_enhance": False, **opts})  def best_path(job: dict, delivered=None, *, root=None):    """The raw render if one exists, else whatever was delivered.     This is the line to use in an asset pipeline: it silently does the right    thing whether or not the finisher chain happened to be on.    """    raw = raw_path(job, root=root)    if raw is not None and Path(raw).is_file():        return Path(raw)    return Path(delivered) if delivered else None

Two rules the pipeline enforces for every asset. Game art goes to the model exactly as written, with Pixal’s prompt enhancer off. And Forge stages the raw render, never the photographic finish (DLSS, de-shine, film grain) that Pixal applies to stills: grain on a flat mascot is damage.

NextWhere it’s heading

Forge is becoming an MCP and API layer: connect the LLM of your choice, ask for assets, and have them rendered on local hardware and delivered straight into a game, without per-render cloud fees. It’s in development. What already works is everything that produced ASTRA BLACK.

03Game

ASTRA BLACK

A creature-card game built around the feeling of opening a pack. Collect creatures, grade the keepers, sell the duplicates and battle your way to a card-show finale.

Status
Live demo, non-commercial
The brief
Push Pixal, driven by LLM agents, to a high-production game in five days
Plays on
Phone + desktop, no account
My part
Creative director, the only human on the team
Play ASTRA BLACK
The released title screen. I drew the wordmark in Figma; the two lead collectors become silhouettes filled with stars.

3.1The playable loop

One pull, four places for it to go.

Every card you pull has somewhere to go in the world. Explore the four connected systems, then try the demo.

Explore a part of the game
An ASTRA PRISM pack being torn open by hand, its top strip pulled away.
The tear · the pull was rolled and saved before this frame

Pacing + trust

Make the reveal worth the wait.

The contents are rolled and saved before the animation. Cards reveal from common to rare, and the anticipation never signals a rarity above the card’s actual tier.

A rival’s partner enters an ASTRA BLACK battle to perform a Signature move.
TAG IN · a partner joins the fight with their Signature

Information + agency

Let the player read the next move.

The rival’s intent is visible before you commit. Shared Momentum and a public token bag give the fight uncertainty while keeping the choices explainable.

Old Oculo’s grading loupe examines a card on the houseboat.
Old Oculo’s houseboat · the loupe walks through the inspection

Reward + persistence

Give a good pull a second moment.

Grading has its own slower rhythm. The grade is saved once before the loupe presentation, so skipping or changing the playback speed cannot reroll the result.

The ASTRA BLACK night-market display case with six card slots and a daily demand bonus.
The night-market stall · six slots, with a daily affinity bonus

Economy + safeguards

Make duplicates part of the loop.

The stall gives spare cards somewhere to go. A listing reserves a specific copy, and sale checks protect enough distinct creatures to keep a battle team playable.

Screens captured from the released browser demo. These controls explore the case study; play the game at astra-black.com ↗.

3.2The reveal

Built around the rip

The first concept note put everything on how opening a pack feels, since a brand-new creature game has no famous characters to lean on. So the rip is a set piece. The pull is rolled and saved before any animation plays. Then you tear a 3D pack by hand and the cards come out worst to best: commons flip fast, and a legendary gets a slam with your collector cutting in on a voiced reaction. Before a rare or better card flips, a ladder of colour climbs (white, rare blue, epic purple, legendary gold) and stops at the card’s real tier, never above it. My note on the first draft of the scripted story pack was that Frost Wyrm didn’t cut it as a first legendary. It now ends on Astragon, a one-of-ten masterpiece with its own serial number.

The pack summary. Every creature is a playable card, with its complete battle kit printed on it.
A sealed First Summons pack on the ASTRA BLACK rip screen
The live game, set to end on a legendary so you see the full reveal. In normal play every pull is rolled. Swipe across the top of the pack, or double-tap. Open full screen

3.3The phone fix

The phone that couldn’t lock in

I picked my team on an iPhone 15 Pro and had no way to start the fight. Safari’s toolbars had pushed the LOCK IN button below the fold, and the creature list swallowed the swipe that should have scrolled the page. The fix went live the same night: the team pick now sizes itself to what the phone actually shows, minus the home-indicator inset, and phone layouts get tested in Playwright WebKit, the engine iOS uses, from 320 × 480 up to 430 × 932. The wide 1400 × 800 layout came from another of my notes. I loved how it played on mobile and wanted that on a big screen too.

The corrected team picker. LOCK IN stays visible while the creature list scrolls.

3.4Art direction

Art direction, written down

Every character needed one distinct visual hook and no look-alikes. The model’s default drew some of the cast too young, so every collector gets an adult build, and the women read as confident and smart. I drew the wordmark myself in Figma. Everything else came from open models run locally: Krea 2 through Pixal for the base art, MatAnyone2 in Forge for the mattes, Qwen Image 2.1 for expressions and seam repairs, Qwen3-TTS for voice clones and Stable Audio 3 for music and effects. I pick the music and most voice takes by ear, and those notes shape the pipeline: one hit sound was re-rolled because its crackle hurt my ears, and a whole voice engine was dropped for being full of artifacts.

Astra City gives each part of the collection loop a place in the world.

3.5The two leads

New faces, same two people.

JunkoThe Street Ripper

  1. Approved art
  2. Anticipation
  3. Disbelief
  4. Ecstatic
  5. Starstruck
  6. Happy tears
  7. Proud

ZiggyThe Deck Drifter

  1. Approved art
  2. Anticipation
  3. Disbelief
  4. Ecstatic
  5. Starstruck
  6. Happy tears
  7. Proud
The approved art, then the six beats of a big first pull, all cut with one fixed box per lead. Only the face moves. Four of the six play in the pack rip today: anticipation, disbelief, ecstatic on an epic and starstruck on a legendary.

Junko and Ziggy are the two main characters: collectors, friends, and each other’s tag-in partner. They had to react to whatever comes out of a pack without turning into someone else, so nothing was regenerated. Every expression is a Qwen Image 2.1 instruction edit of the approved art, run locally through Pixal, never a fresh generation. The prompt names the one thing that may change, the face, and lists everything that may not: pose, hands, hat, hair, cards and clothing. There is no mask, because a painted mask would switch Pixal to its inpainting lane.

Forge checks each result before it installs. The silhouette has to match the source, and the twelve full-body expressions scored between .992 and .996 against a .90 gate. The edge ring has to stay clean, and a face-crop sheet at one fixed box shows whether the face drifted. The first set shipped with a faint alpha haze from the edit model, so MatAnyone2 re-keyed all of them and took it from .59 to .01. The full-body art underneath was outpainted in Pixal on September 17, because the original renders cut off the soles, the braid ends and the skateboard.

The idle loops came from MiniMax H3, an open-weight video model running locally through Pixal: five seconds from the approved still, generated as a loop. MatAnyone2 keys every frame, and Forge aligns the first frame to the still so the clip lands exactly over the cut-out it replaces. The game ships it as VP9 with alpha, plus an H.264 twin for Safari that carries colour on top and alpha below, recombined in a WebGL shader.

The shipped idle loops, keyed by MatAnyone2 and shown here on the page. In the game they play on the hub stall and the character select.
Edit
Qwen Image 2.1 via Pixal, no mask · 25 steps · 2 MP · full-body set on one seed, busts on two
Gate
Silhouette match ≥ .90 · clean edge ring · fixed face-crop sheet
Re-key
MatAnyone2 on a #808080 plate · haze .59 → .01
Motion
MiniMax H3, local · 5.17 s loop · 24 fps · alpha WebM + Safari twin

3.6Decisions from the playtests

Three calls that changed the build.

  1. 01

    Luck, with limits

    The first battle rules had no randomness at all. When I played them it was punch, punch back, and as an average player I couldn’t tell what had just happened. Collector Clash 0.2 kept the readable creature kits and added bounded luck: one Momentum pool across your team, a small public bag of STRIKE, GUARD, RECOVER and TAG tokens, and hit grades from MISS to PERFECT. In simulation the ladder runs from a first fight that random button presses still win 38% of the time to a boss at about a coin flip.

  2. 02

    Nothing ships unseen

    ASTRA PRISM, the second set, once shipped with its packs falling back to the flat 2D rip; the notes had said “not verified”. The 3D hotfix went out 19 minutes later, and my rule since then is that a feature nobody has seen working on screen blocks the release. Scripted gates run first in a clean browser, and the art gate exists because I hit two creatures in a row whose card crop showed ears where the face should be. When we looked, 77 of the 88 had no face point at all.

  3. 03

    Three models, one source of truth

    Claude and an OpenAI Codex agent worked in the same repository, and Claude handed parts of the battle engine to Moonshot’s Kimi K3, then diffed every returned file before it landed. What kept them from undoing each other was plain paperwork: an SSOT file both agents read first, and a dated learnings log where every process failure gets a rule. One came from the story ladder: the card-show finale had ended up as the first fight because it was tuned easy. The rule since is to order fights by story, then tune.

3.7On a phone: the live build, captured from astra-black.com

Title: the wordmark strikes together on the tap
The stall on a brand-new save
A sealed ASTRA PRISM pack
Team pick, with LOCK IN on screen
Planning: the rival’s move is shown first
A finisher lands
K.O. on the main stage
A slab back from Old Oculo: 9, MINT

3.8From the live build · written by the agents

Where the design rules live.

packrip/stage.js
// Photosensitivity guard: at most two flash peaks in any rolling second.const FLASH_WINDOW = 1;const FLASH_COUNTS = 0.15;// …pulse(field, peak, duration = 0.4, easing = ease.outCubic) {  // …  if (field === "flash" || field === "exposure") {    const rise = peak - (field === "flash" ? 0 : base);    const counts = field === "flash" ? peak >= FLASH_COUNTS : rise >= 0.35;    if (this.reducedMotion) {      if (field === "flash") return RESOLVED;      peak = Math.min(peak, base + 0.15);    } else if (counts && !this.flashAllowed()) return RESOLVED;  }  // …} flashAllowed() {  const ft = this.flashTimes;  const oldest = ft[0] <= ft[1] ? 0 : 1;  if (this.clock - ft[oldest] < FLASH_WINDOW) return false;  ft[oldest] = this.clock;  return true;}

The photosensitivity cap. A legendary reveal asks for a double flash; the stage allows at most two flash peaks in any rolling second, and with reduced motion on there is no flash at all.

StatusSeptember 2026

Where each layer stands.

Engine · Pixal · Released

A Windows installer, a conversational studio, inspectable recipes, image editing, animation and sound. What I’d measure next: fresh-install completion, time to the first successful render, and how often people fix a result without starting over.

Factory · Forge · In development

Batch rendering through Pixal, mattes, the art gate and theme packing all work, and all of it is proven on ASTRA BLACK. Next is the MCP and API layer, so the LLM of your choice can request assets and deliver them into a game.

Game · ASTRA BLACK · Live demo

Single-player, saves in the browser, no accounts and no real money. Bots walked all 4,294,967,296 pack-generator outputs and rolled 9 million packs with zero violations, and a fuzzer ran 30,000 battles. The same bots found the next job: rarity barely changes combat, and a patient seller can earn back 126% of a pack’s price.