Docs/@katforge/forge

@katforge/forge

The game engine runtime. Manages the game loop, input mapping, asset loading, and rendering (via Pixi.js). Provides Vue composables so game systems bind directly to reactive state.

shell
npm install @katforge/forge

Architecture

flowchart TB
   subgraph forge["@katforge/forge"]
      Loop["Loop<br/>(fixed timestep)"]
      Input["Input<br/>(keyboard, gamepad)"]
      Assets["Assets<br/>(sprites, audio)"]
      Renderer["Renderer<br/>(Pixi.js)"]
   end

   subgraph vue["Vue composables"]
      useGameLoop
      useQuery
      useComponent
      useObserver
   end

   subgraph anvil["@katforge/anvil"]
      World
   end

   Loop -->|tick| World
   useGameLoop -->|bind| Loop
   useQuery -->|reactive| World
   Renderer -->|draw| World
   Input -->|actions| World

Loop runs the fixed-timestep update + variable-render schedule. Input translates raw keyboard / gamepad events into named actions, polled by systems each tick. Assets is a typed loader that resolves keys to sprites and audio buffers. Renderer wraps Pixi.js and the sprite-syncing helper. The Vue composables in the middle layer (useGameLoop, useQuery, useComponent, useObserver) bridge the loop to reactive state so components re-render automatically when world data changes.

Game loop

Fixed timestep with variable render interpolation:

TypeScript
import { Loop } from '@katforge/forge';

const loop = new Loop ({
   fps:    60,
   update: (dt) => world.tick (dt),
   render: (alpha) => renderer.draw (world, alpha)
});

loop.start ();

Vue composable

TypeScript
import { useGameLoop } from '@katforge/forge';

const { world, running, fps } = useGameLoop ({
   fps: 60,
   setup:  (world) => { /* spawn initial entities */ },
   update: (world, dt) => { /* run systems */ },
   render: (world, alpha) => { /* draw */ }
});

A system wired into the loop

A typical movement system reads input each tick, mutates Velocity, and lets a downstream system apply velocity to position:

TypeScript
import { Position, Velocity } from './components';

function movementInput (world, input, dt) {
   for (const [ vel ] of world.query (Velocity, Player)) {
      vel.dx = input.held ('move_right') ? 200 : input.held ('move_left') ? -200 : 0;
      vel.dy = input.held ('move_down')  ? 200 : input.held ('move_up')   ? -200 : 0;
   }
}

function applyVelocity (world, dt) {
   for (const [ pos, vel ] of world.query (Position, Velocity)) {
      pos.x += vel.dx * dt;
      pos.y += vel.dy * dt;
   }
}

useGameLoop ({
   fps: 60,
   update: (world, dt) => {
      movementInput (world, input, dt);
      applyVelocity (world, dt);
   }
});

System ordering inside update is the contract — there's no auto-scheduler. Two systems that touch the same component should be sequenced explicitly.

Input

TypeScript
import { Input } from '@katforge/forge';

const input = new Input ({
   actions: {
      move_up:    [ 'KeyW', 'ArrowUp' ],
      move_down:  [ 'KeyS', 'ArrowDown' ],
      attack:     [ 'Space' ],
      interact:   [ 'KeyE' ]
   }
});

// In a system
if (input.pressed ('attack')) { /* ... */ }
if (input.held ('move_up'))   { /* ... */ }

Assets

TypeScript
import { Assets } from '@katforge/forge';

const assets = new Assets ();
await assets.load ([
   { key: 'hero',   type: 'sprite', url: '/sprites/hero.png' },
   { key: 'slash',  type: 'audio',  url: '/sfx/slash.ogg' }
]);

const heroTexture = assets.get ('hero');

Renderer

Wraps Pixi.js with ECS integration. The createSpriteSystem auto-syncs Pixi sprites with entities that have Position and SpriteComponent:

TypeScript
import { PixiRenderer, createSpriteSystem } from '@katforge/forge';

const renderer = new PixiRenderer ({ width: 1280, height: 720 });
const spriteSystem = createSpriteSystem (renderer, world);

Reactive queries (Vue)

TypeScript
import { useQuery, useComponent } from '@katforge/forge';

// Reactive list of entities matching Position + Health
const entities = useQuery (world, Position, Health);

// Reactive single component for a specific entity
const hp = useComponent (world, playerId, Health);
// hp.current is a Vue ref