Sparze ECS Framework Expert is a development claude skill built by Neo. Best for: Game developers and systems programmers use this to architect Sparze ECS applications with proper entity lifecycle management, deferred command patterns, and query optimization..

What it does
Build high-performance Entity Component System applications in Zig using Sparze with compile-time safety and performance optimization.
Category
development
Created by
Neo
Last updated
Claude Skilldevelopment GitHub-backed CuratedadvancedClaude Code

Sparze ECS Framework Expert

Build high-performance Entity Component System applications in Zig using Sparze with compile-time safety and performance optimization.

Skill instructions


name: sparze description: Expert guidance for Sparze ECS apps including systems, queries, components, resources, events, and performance trade-offs.

Sparze ECS Skill

Expert guidance for building high-performance ECS applications with Sparze.

Core Concepts

World: ECS coordinator with compile-time component, resource, event, and group types.

const World = sparze.World(
    .{ Position, Velocity, Health },     // Components
    .{ DeltaTime, Score },               // Resources
    .{ CollisionEvent },                 // Events
    .{ struct { Position, Velocity } },  // Groups
);

Entity: 32-bit handle [version:16 | index:16]. index selects dense slot; version invalidates stale handles. Use .index, .version, or getIndex() / getVersion(). Create via Entity.init(), serialize with toInt() / fromInt(u32).

Components: Data attached to entities. Tag components (empty structs) use 1 bit per entity.

Resources: Global singletons injected via Resource(T) / ResourceMut(T). Can implement init() / deinit(), or opt out with pub const auto_init = false (manual initialization required). POD resources are zero-initialized for compatibility.

Events: 1-frame delayed, double-buffered communication. Events written in frame N are read in frame N+1.

Docs: @docs/ARCHITECTURE.md, @docs/ENTITY_LIFECYCLE.md

System Functions

System functions use injected parameters and must use commands: anytype for world mutation. Return void or !void.

fn mySystem(
    allocator: std.mem.Allocator,
    movement: Group(struct { Position, Velocity }),
    health: SingleQuery(Health),
    delta: Resource(DeltaTime),
    score: ResourceMut(Score),
    reader: EventReader(CollisionEvent),
    writer: EventWriter(DamageEvent),
    commands: anytype,
) !void {
    // Implementation
}

Commands API

Immediate (returns now):

  • commands.createEntity()
  • try commands.createEntityWith(.{ Position{...}, Velocity{...} })
  • commands.setResource(T, value) / commands.getResource(T) / commands.getResourcePtrMut(T)
  • try commands.initResources(.{ ... })
  • try commands.serializeToFile(path) / try commands.deserializeFromFile(path)

Deferred (applied at world.endFrame()):

  • try commands.addComponent(e, T, value) / try commands.removeComponent(e, T)
  • try commands.addTag(e, T) / try commands.removeTag(e, T)
  • try commands.destroyEntity(e)

Why deferred: avoids invalidating iterators or shifting dense arrays mid-iteration.

Docs: @docs/SYSTEM_PATTERNS.md

Queries

Decision guide:

  • SingleQuery: single component, any frequency
  • Query: multi-component, occasional use
  • Group: multi-component, hot path

SingleQuery(Component)

fn healthSystem(query: SingleQuery(Health)) !void {
    for (query.entities, query.components) |entity, *health| {
        health.hp = @max(0, health.hp);
    }
}

Query(struct { ... })

fn combatSystem(query: Query(struct { Position, Health, ?Shield })) !void {
    for (query.entities) |entity| {
        if (query.filter(entity)) {
            const pos = query.getComponent(entity, Position);
            const health = query.getComponentMut(entity, Health);
            const shield = query.getOptional(entity, Shield);
            _ = pos; _ = health; _ = shield;
        }
    }
}

Group(struct { ... })

const MovementGroup = struct { Position, Velocity };
const World = sparze.World(.{ Position, Velocity }, .{}, .{}, .{ MovementGroup });

fn movementSystem(group: Group(MovementGroup)) !void {
    const positions = group.getMutArrayOf(Position);
    const velocities = group.getArrayOf(Velocity);
    for (positions, velocities) |*pos, vel| {
        pos.x += vel.x;
        pos.y += vel.y;
    }
}

Free(Component): use in groups that do not own a component. Access via group.getComponent(entity, T) instead of array access.

Tag Queries

fn enemySystem(query: TagQuery(struct { Enemy, ?Boss, Exclude(Dead) })) !void {
    for (query.entities) |entity| {
        if (query.filter(entity)) {
            _ = entity;
        }
    }
}

Docs: @docs/QUERY_PATTERNS.md

Query Modifiers

  • Optional (?T): entity may or may not have component
  • Exclude(T): filter out entities with component
  • Free(T): group does not own component; access by lookup

System Organization

Principles:

  • One system, one responsibility
  • Writers run before readers
  • Use events for loose coupling (1-frame delay)
  • Group systems by domain (physics, combat, rendering)

Frame lifecycle:

world.beginFrame();
try world.runSystem(inputSystem);
try world.runSystem(physicsSystem);
try world.runSystem(renderSystem);
try world.endFrame();

Critical: Always call endFrame() to flush deferred commands.

Resources

Initialization options:

  • init() / deinit() for auto-lifecycle
  • pub const auto_init = false for manual setup
  • POD resources are zero-initialized

Safety helpers:

  • isResourceInitialized(T)
  • tryGetResource(T)
  • initResources(.{ ... }) for bulk init

Performance

Fastest to slowest:

  1. Group owned components (getMutArrayOf)
  2. SingleQuery
  3. Group free components (getComponent)
  4. Query

Best practices:

  • Use Group for hot-path systems
  • Use Query for ad-hoc/dynamic access
  • Validate group ownership with World.validateGroups()

Storage notes:

  • Tag components use 1 bit per entity
  • Reserve with getSparseSetPtrMut(T).reserve(capacity)
  • Pagination: 4096 entities per page

Docs: @docs/PERFORMANCE.md, @docs/STORAGE_INTERNALS.md

Quick Reference

| Task | API | |------|-----| | Create entity | commands.createEntity() | | Add component | try commands.addComponent(e, T, value) | | Query single | SingleQuery(Health) | | Query multi | Query(struct { Position, Velocity }) | | Fastest iteration | Group(struct { Position, Velocity }) | | Optional component | Query(struct { Position, ?Sprite }) | | Exclude entities | Query(struct { Enemy, Exclude(Dead) }) | | Read resource | Resource(DeltaTime) | | Write resource | ResourceMut(Score) | | Read events | EventReader(CollisionEvent) | | Write events | EventWriter(DamageEvent) | | Tag iteration | SingleTag(Enemy) |

Documentation Index

  • @docs/ARCHITECTURE.md
  • @docs/QUERY_PATTERNS.md
  • @docs/ENTITY_LIFECYCLE.md
  • @docs/STORAGE_INTERNALS.md
  • @docs/SYSTEM_PATTERNS.md
  • @docs/PERFORMANCE.md

Use this skill

Most skills are portable instruction packages. Claude Code supports SKILL.md directly. Other agents can use adapted files like AGENTS.md, .cursorrules, and GEMINI.md.

Claude Code

Save SKILL.md into your Claude Skills folder, then restart Claude Code.

mkdir -p ~/.claude/skills/sparze-ecs-framework-expert && curl -L "https://raw.githubusercontent.com/glassesneo/sparze/c3197afd2ce8ee12d477b4fcf298d73f43fe0cdd/SKILL.md" -o ~/.claude/skills/sparze-ecs-framework-expert/SKILL.md

Installs to ~/.claude/skills/sparze-ecs-framework-expert/SKILL.md.

Use cases

Game developers and systems programmers use this to architect Sparze ECS applications with proper entity lifecycle management, deferred command patterns, and query optimization.

Reviews

No reviews yet. Be the first to review this skill.

No signup required

Stats

Installs0
GitHub Stars2
Forks0
LicenseMIT License
UpdatedJan 5, 2026