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
Pixal 2.1.1b for Windows · ASTRA BLACK on phone and desktop, no account needed
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
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.
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.
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.
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.
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.
- 01
Your intent
Prompt · model · style · reference · canvas
Creative choices - 02
A compatible recipe
Encoder · sampler · scheduler · graph
Inspectable defaults - 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
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.
Private while in development
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.
Engine · Pixal
Renders stills, video and audio. Nothing above it edits it.
Factory · Forge
Batches the renders, keeps the raw frame, mattes, gates and packs a theme.
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.
- 01
Render
Pixal runs Krea 2 with style LoRAs, on a flat #808080 plate so glossy parts keep their true colour.
- 02
Pick
An agent shortlists the best of each batch. I overrule it when I disagree.
- 03
Matte
MatAnyone2 keys the cut-out with anti-aliased edges, and glow survives as partial alpha.
- 04
Gate
A script fails any alpha haze and any creature without a face point. Last release pass: 442 images, 0 failures.
- 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.
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.
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.
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.
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.
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.
3.5The two leads
New faces, same two people.
JunkoThe Street Ripper
- Approved art
- Anticipation
- Disbelief
- Ecstatic
- Starstruck
- Happy tears
- Proud
ZiggyThe Deck Drifter
- Approved art
- Anticipation
- Disbelief
- Ecstatic
- Starstruck
- Happy tears
- Proud
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.
- 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.
- 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.
- 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.
- 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
3.8From the live build · written by the agents
Where the design rules live.
// 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.