Holonic Asset: Architectural Design for AI Game Asset Production

#Holonic Asset
#System Architecture
#Game Development
#AI Engineering
#Modular Monolith

Why Most AI Image Tools Fail at Real “Game Asset Production”

Engineers who have worked on game development often share an intuitive realization: there is a massive engineering chasm between generating a dazzling one-off AI image and producing real, shippable game assets.

The core interaction of most consumer AI image generation tools is straightforward: “Enter a prompt, wait 15 seconds, and get an image.” This workflow is fine for concept art or one-off illustrations, but as soon as it enters a real game production pipeline, it immediately hits several hard constraints:

  1. Style and Perspective Drift: A game requires an entire suite of cohesive assets to construct its world. If you create the protagonist, then enemies, then environments and props, relying purely on memory to type prompts will quickly result in a side-view character, a top-down 2.5D environment, and props with mismatched color palettes and line weights.
  2. Structural Heterogeneity: A game asset is rarely just “a flat picture.” A character asset contains prototype art, directional poses, and animation frame sequences; a scenery asset contains layered backgrounds; a tileset contains slicing rules and collision grids; a UI kit contains individual components. Treating them all as flat PNGs makes fine-grained editing, frame slicing, and engine integration nearly impossible.
  3. Async Latency vs. Active Creative Cadence: Generating an animation sequence can take 5 minutes or even longer. Creators cannot be forced to stare at a blocking spinner; yet if they continue tweaking the active canvas while the AI runs in the background, the finished task must never silently overwrite the manual adjustments they made during that time.
  4. Traceability and Experimentation Costs: AI generation is inherently stochastic. Creators need the safety to experiment, undo, compare variations, and solidify a satisfying iteration into an immutable historical version.

Holonic Asset was designed from the ground up to resolve these tensions: not treating AI as a one-off image generator, but engineering a production system where game assets can be continuously defined, produced, organized, and iterated.


Overall Architecture: One Deployment Unit, Two Collaborative Pipelines

Before diving into Project, Asset, Task, and Record, let us anchor these concepts within the real codebase. Holonic Asset is far more than “a frontend calling a generation API”—it is a full production system composed of Frontend, Backend, persistent infrastructure, and external model capabilities.

Holonic Asset Overall Architecture

From a deployment perspective, the system operates across three primary boundaries:

  • Frontend: Houses the project asset library, canvas editor, and generation task drawer. Working Content, Undo/Redo history, and unapplied generation previews reside in the browser session; routine requests use the unified contract API, while large files are uploaded directly to object storage via presigned credentials.
  • Backend: Hosts API ingress, the Outbox Dispatcher, and Task Workers, partitioned cleanly by domain into Workspace, Generator, Task, Processor, Upload, and Repository modules. Modules communicate via Go interfaces rather than network RPCs; our rationale for a single deployment artifact is detailed below.
  • Infrastructure & External Capabilities: PostgreSQL holds domain entities (Project, Asset, Record) as well as Task queues and Outbox events; object storage stores images, videos, and sprite sheets; external LLM, image, and video providers handle stochastic generation, while local Processors handle deterministic frame slicing, background removal, palette matching, and dimension normalization.

When our team initially decomposed backend modules—identifying Workspace, Task, Generator, Processor, Upload, and Export—it was easy to immediately think of microservices: breaking each module into an independent service communicating over gRPC or message queues.

Generation Call Chain Under a Microservice Topology

Had we split these modules into microservices early on:

  • What was originally an atomic local transaction (“business state update + Task/Outbox creation”) would turn into a distributed consistency problem;
  • Every module would incur operational overhead for service discovery, retries, distributed tracing, circuit breaking, and independent release pipelines;
  • Clear code interfaces would be prematurely promoted into long-lived network protocols while domain models were still rapidly evolving.

Therefore, we adopted clean code boundaries under a unified deployment boundary: business modules maintain strict isolation in code while sharing a transactional boundary and in-process execution chain at runtime.

Practical Modular Monolith Implementation

  • Strict Domain-Driven Packaging: workspace (project + asset), task, generator, processor, and storage maintain explicit interfaces;
  • In-Process Interface Collaboration: Generators register Handlers directly into the Task engine without coupling Task to internal generator logic;
  • Shared Transactional Boundaries: Ingress handlers, Task Workers, and Outbox Dispatchers run in the same process, sharing database connections and transactional contexts to simplify consistency guarantees.

When Will We Split Services?

We only split when driven by clear physical or organizational constraints:

  1. Media Processing Requires Independent Auto-Scaling: When FFmpeg frame extraction, upscaling, or compute-heavy image algorithms become a bottleneck, processor will be extracted into a dedicated compute worker pool;
  2. Self-Hosted Models Require Heterogeneous Environments: When inference relies on dedicated Python/CUDA/GPU clusters and scheduling, specific Providers will be isolated behind dedicated inference microservices;
  3. Domain Ownership Splits Across Independent Teams: When organizational boundaries stabilize and modules require independent release cadences, deployment boundaries will follow team boundaries.

Until those conditions are met, a modular monolith is the most pragmatic deployment architecture for this level of complexity.


Project: Turning Ephemeral Mental Rules into a System Kernel

Without a unified project abstraction, a creator building a set of pixel art assets typically suffers through a tedious workflow:

Every time they open a generation modal, they must copy-paste a repetitive string of prompt modifiers: "16-bit pixel art, side-view 2D platformer, NES palette, 32x32 grid...". If a single keyword is accidentally omitted, or the model interprets a term differently, the newly generated sprite visibly clashes with existing assets.

The fundamental flaw in this approach is that we offload cross-asset global constraints onto the creator’s memory and fragile prompts, rather than enforcing them in the system layer.

Project Creative Kernel

In Holonic Asset, a Project is not just a folder for grouping files—it is the shared “Creative Kernel” across all associated assets.

At the Project level, we centralize durable, cross-asset metadata:

  • GameType (game genre: e.g., whether card game or RPG largely dictates the overall art style)
  • Perspective (spatial rules: e.g., 2D Side-scroll, Top-down 45°, Isometric)
  • Target Platform (target platform: PC vs. Mobile)
  • Reference Images (reference images: containing various project elements to serve as the project’s foundation)

Separation of Concerns in System Design

The core architectural principle here is: The Project manages only project-level context. It remains completely agnostic to the internal contents of any specific Asset, and indifferent to how a given generation task invokes downstream models.

When a creator requests a generation on a specific asset (such as the player character), they focus purely on the immediate, localized intent (e.g., "swinging a sword to the right"). The backend automatically merges the global Project context with the Asset’s parameters when assembling the job for the Generator.

This eliminates repetitive prompt crafting and structurally prevents gradual style drift across the project.


Asset: Standardizing the Lifecycle While Preserving Structural Diversity

A common architectural trap is “flattening genuine domain differences for the sake of generic abstraction.”

In a game pipeline, data structures vary drastically across asset types:

  • Character / Object Asset: contains base prototype art, multi-directional poses, and animation frame sequences (Run/Attack/Die);
  • Scenery Asset: contains foreground, midground, and background layers with depth ordering;
  • Tileset Asset: contains the master sheet, sliced tiles, metadata, and collision grids;
  • UI Asset: contains component states and layout structures.

If we treat them all as generic image files, the system cannot offer frame playback, layer toggles, or grid slicing. Conversely, if we build siloed CRUD, tagging, versioning, and task pipelines for each asset type, we end up duplicating massive amounts of boilerplate infrastructure.

To balance this, we split an Asset into two dimensions with completely different reasons to change:

Asset Attributes and Contents

1. Attributes (Metadata & Constraints)

Attributes define an asset’s “external identity and governance rules”: Asset ID, Name, Project ID, Asset Type, Tags, Perspective, and strongly-typed Dimensions constraints (e.g., {"width": 64, "height": 64} for Character / Object / Scenery, {"tileSize": {...}, "tileAmount": {...}} for TileSet). It answers: “What is this asset, and what system rules and dimensional constraints govern it?” All assets, regardless of type, share a uniform lifecycle interface: create, query, rename, tag, duplicate, delete, and commit versions.

2. Contents (Domain Structure & Concrete Resources)

Contents represent the actual “structured payload” of the asset, modeled with dedicated domain schemas:

  • Character / Object stores frame sequences and action mappings;
  • Scenery stores layer trees and parallax depth configs;
  • Tileset stores tile bounds and collision mappings.

It answers: “What concrete resources does this asset contain right now?”

Why This Decoupling Matters

This separation lets each part of the system focus on its core responsibility:

  • Backend Asset Management Modules only need to understand Attributes to handle workspace-wide search, filtering, tagging, and bulk operations;
  • Canvas Editors and Generators focus strictly on their respective Contents schemas without worrying about asset lifecycles;
  • Adding a brand-new asset type (such as a UI Set) only requires defining a new Contents schema—all existing project contextualization, tagging, snapshots, and task scheduling work out of the box.

Task & Generator: Async Long-Running Orchestration with Non-Intrusive State Projection

Generating AI assets involves heavy external pipelines (LLM planning, diffusion model inference, video synthesis), often taking anywhere from several seconds to over half a minute.

Synchronous endpoints would freeze the UI. But even with an asynchronous architecture, poor boundary design can quickly lead to three classic traps:

  1. Coupling task plumbing with domain logic: If the generic Task queue directly handles “how to slice character animation frames” or “how to extract scenery layers,” it rapidly mutates into a bloated god-class full of switch-case statements.
  2. Blindly overwriting active canvas state: A user clicks generate, and during the 30-second wait, makes manual tweaks to the canvas. If the finished task automatically updates the database asset, the user’s manual adjustments are destroyed.
  3. Exposing raw backend persistence states to the UI: Flooding the client with all internal lifecycle states (cancellations, intermediate queue transitions, cleanup jobs) clutters the UI with useless noise.

To prevent this, we decoupled Task (job execution backbone) from Generator (localized domain orchestration).

Backend Task State Machine and Frontend Projection

Backend: Complete Six-State Lifecycle

On the backend, Task acts as a generic envelope responsible for queue management, worker execution, retries, and persistence:

  • pending: task is queued in the database waiting for a worker;
  • processing: worker has claimed the job and is executing the registered Generator Handler;
  • awaiting_application: generation succeeded, but the result is held in the Result field awaiting user review;
  • completed: user explicitly accepted/rejected the result, or an automatic task finished;
  • failed: an error occurred; task records error details and awaits retry or cancellation;
  • cancelled: user explicitly dismissed the failed task, transitioning to a terminal state.

Generator: Localized Domain Orchestration

Generators contain the actual domain intelligence. For example, AnimationGenerator:

  1. Accepts only the minimal required inputs (prototype reference, action direction, frame count, parameters);
  2. Calls external AI providers (ComfyUI / Flux / video models);
  3. Invokes deterministic local Processors (background removal, palette matching, FFmpeg frame slicing, grid alignment);
  4. Uploads static assets to object storage;
  5. Yields only a localized Content Patch with resource URLs.

The Generator never needs to read or write the entire Asset entity.

Frontend State Projection and the awaiting_application Shield

To maintain a clean, non-intrusive creator experience, the frontend does not mirror the backend’s entire state machine. Instead, it uses on-demand state projection:

  • The client only surfaces 4 actionable states: pending, processing, awaiting_application, and failed.
  • When a task reaches awaiting_application, the frontend overlays the generated Content Patch on top of the user’s active working copy for non-destructive preview.
  • Clicking Accept merges the patch into the local draft; clicking Reject discards the patch and triggers backend cleanup. In both cases, the task cleanly transitions to completed and fades from the active task drawer.

Core Rule: AI output is strictly an advisory proposal; the human creator always retains final veto power.


Record: Why We Chose Full Snapshots Over Git-Style Diffs

When designing a version control system for assets, an engineer’s first instinct is often: “Should we build a Git-like DAG engine with JSON patch diffs to store incremental changes?”

However, after examining domain realities, we firmly rejected the Git diff route in favor of Copy-on-Write immutable full Content snapshots (Records).

Working Copy Draft vs. Immutable Record Snapshot

Why Reject the Diff Approach?

  1. High Maintenance Cost of Schema Evolution: Asset Contents (animation state machines, layer trees, slicing grids) evolve rapidly across product iterations. With diffs, the backend must permanently maintain backwards-compatible patch replay and schema migration logic. A single corrupted or incompatible patch in the chain breaks all subsequent history.
  2. Negligible Storage Savings: The heavy payloads in game assets are images, sprite sheets, and audio files—all stored in object storage (S3/OSS) and referenced by CDN URLs. The Contents payload itself is merely a small JSON document containing these URLs and layout metadata (usually just a few KB). Introducing a complex diff/patch engine just to save a few kilobytes of JSON is textbook over-engineering.

Clear Separation of Drafts and History

We strictly divide ephemeral canvas manipulation from committed history:

Active Canvas Manipulation ≠ Saved
Clicking Accept on an AI Result ≠ Saved
Only Explicit User Save Creates a New Record Snapshot
  • Frontend: Mutable Working Content Handles rapid canvas interactions, local Undo/Redo stacks, and merging AI patches.
  • Backend: Immutable Content Record When the user explicitly clicks Save, the frontend submits the full Content snapshot. The backend validates concurrency baselines and writes an immutable snapshot (Record vN+1) via Copy-on-Write. Previous snapshots are never mutated.

Creators can freely experiment or break things on the frontend canvas knowing that committed history remains rock-solid.


End-to-End Collaboration: A Character Animation Closed Loop

Connecting these components together, let us trace what happens when adding a walk animation to an existing character:

Adding Animations to an Existing Character End-to-End Flow

  1. Dispatch Task: The creator selects a Character Asset, configures animation parameters, and submits. Core API reads the persisted Prototype and Project context, creates the Task and Transactional Outbox records within a single database transaction, and immediately returns a Run ID. The frontend remains unlocked.
  2. Background Execution & Deterministic Processing: A Task Worker claims the job. AnimationGenerator prepares domain inputs, invokes external models, runs local Processors for background removal and frame extraction, and stores the frames in object storage.
  3. Isolated Staging: The worker writes frame data into Task Result and marks the task as awaiting_application. The asset itself remains untouched.
  4. Preview & Merge: The frontend receives a notification and renders a live preview by overlaying the Task Result on top of the creator’s latest Working Content.
  5. Resolution & Snapshot:
    • If the user clicks Accept, frames merge into the active draft, and the task transitions to completed;
    • Once satisfied, the user clicks Save. The frontend submits the complete snapshot; the backend validates versions and commits a new immutable Record.

Architecture Decision Matrix

The table below summarizes the key trade-offs made throughout the design of Holonic Asset:

Decision PointChosen ApproachRejected AlternativeCore RationaleAccepted Trade-off
Project ContextCentralized Creative KernelRepeated prompts per AssetReusable art style, perspective, and palette rules across assets prevent prompt driftSystem must maintain context inheritance and injection pipelines
Asset ModelUniform Attributes lifecycle, isolated heterogeneous ContentsSiloed models per type or treating all assets as flat filesBalances generic management (tagging, search, duplication) with unique domain structuresGeneric layer must accommodate dynamic/heterogeneous Contents schemas
Task OrchestrationDecoupled Task state machine and GeneratorsAd-hoc queues per generation featureQueuing, retries, and errors are generic infrastructure; generation logic is volatileMust maintain generic task envelope protocols and handler registries
Interaction & StatesFull 6-state backend, 4-state client projectionSingle mirrored state across backend and frontendBackend requires complete lifecycle for auditing; creators need minimal UI noiseFrontend must maintain a state projection mapping layer
AI Output HandlingExplicit awaiting_application reviewAutomatic direct writeback to AssetPrevents AI results from overwriting concurrent manual canvas editsRequires localized patch overlays and accept/reject flows
Version HistoryCopy-on-Write full Content snapshotsGit-style diff/patch chainsContents are lightweight JSON referencing media URLs; avoids fragile schema replay enginesTolerates minor structural JSON redundancy across snapshots
Deployment BoundaryGo Modular Monolith + Local OutboxMicroservices per business domainLatency bottleneck is external providers; microservices add network and consistency overheadModules share release cycles, requiring strict internal package discipline

Final Thoughts

In the current AI landscape, much of the hype centers around raw model parameter sizes and the magic of one-off generations.

Yet once you integrate AI into a professional production workflow, you quickly discover: what truly makes a tool usable is rarely the raw model alone, but the engineering scaffolding built around it.

  • How do you save creators from repeatedly writing prompts?
  • How do you keep long-running async generation from interrupting hands-on editing?
  • How do you make every generation result predictable, reviewable, and reversible?
  • How do you maintain architectural simplicity and discipline as features rapidly evolve?

The architecture of Holonic Asset represents our engineering answer to these challenges in the context of real-world game asset production.