@aivoland/game-engine
v0.0.1
Published
Pure TypeScript turn-based game engine with a ruleset architecture. No UI, network, storage, timer, or runtime dependencies.
Downloads
114
Readme
@aivoland/game-engine
Pure TypeScript turn-based game engine with a ruleset architecture. No UI, network, storage, timer, or runtime dependencies.
Rules & Guardrails
- Never couple this package to React, Vue, DOM, Canvas, HTTP, WebSocket, or any database.
- Never call Agent/LLM APIs or generate UI text inside ruleset logic.
- Never let concrete ruleset code touch
coreconcepts, and never let one ruleset depend on another. - Do not use
class,Map,Set,Date, or functions inside game state objects — state must be plain, serializable objects. - Do not introduce randomness inside rule functions; callers must inject seeds or pre-rolled results.
- Only promote a utility to
shared/when two or more rulesets share it. - Run
pnpm run check:testbefore committing any ruleset change.
Core Project Context
- Package:
@aivoland/game-engine— standalone logic library, zero runtime dependencies. - Repository root:
packages/game-engine/ - Source layout:
src/index.ts— stable public exports onlysrc/core/— generic interfaces,GameSession, result/error/event base typessrc/rulesets/<name>/— one directory per game rulesetsrc/shared/— cross-ruleset utilities (geometry.ts,grid.ts,ids.ts)
- Commands:
- Lint:
pnpm run check:lint - Type-check:
pnpm run check:type - Dependency graph:
pnpm run check:dep - Test:
pnpm run check:test
- Lint:
Architecture Notes
IGameRuleset<TState, TAction, TView, TEvent>(src/core/types.ts) — the contract every ruleset must implement:createInitialState(): TStategetCurrentActor(state): stringgetLegalActions(state): TAction[]applyAction(state, action): IApplyActionResult<TState, TEvent>getView(state, actorId): TViewisGameOver(state): IGameOverResult | null
GameSession(src/core/session.ts) — thin stateful wrapper around a ruleset; holds current state and accumulated event log. Use it as the external drive loop:while (!session.isGameOver()) { const view = session.getView(session.getCurrentActor()); const action = await agent.decide(view, session.getLegalActions()); session.applyAction(action); }- State rules: plain objects only; no class instances, no
Map/Set/Date, no embedded functions. State must be JSON-serializable for replay and branching. - Rule functions: pure — take old state + action, return new state + events + feedback. No side effects, no external reads.
IApplyActionResult(src/core/result.ts):{ state, events, feedback }wherefeedbackcarriessuccess,apConsumed,apRemaining, andwarnings.IGameOverResult(src/core/result.ts):{ winner, scores, reason }wherereasonis'turn-limit' | 'elimination'.IGameEvent(src/core/events.ts): base shape{ type, globalSlot, turn, actorId }— all ruleset events extend this.IGameError(src/core/errors.ts):{ code: 'illegal-action' | 'invalid-action' | 'game-over', message }.
Public Exports (src/index.ts)
export { GameSession } from '~/core/session';
export type { IGameRuleset } from '~/core/types';
export type { IActionFeedback, IApplyActionResult, IGameOverResult } from '~/core/result';
export { resourceClashRuleset } from '~/rulesets/resource-clash';
export type { IResourceClashEvent } from '~/rulesets/resource-clash/events';
export type {
IEnemyMemory, IFaction, IFactionResources, IOutpost,
IResourceClashAction, IResourceClashState, IResourceClashView,
ISourceType, IUnit, IUnitType, IVisibleSource,
} from '~/rulesets/resource-clash/types';Only export from src/index.ts. Do not import internal modules directly from consuming packages.
Coding Style
- Language: TypeScript strict. Prefer async/await; avoid
voidreturn annotations. - Interfaces and types: MUST be prefixed with
I(e.g.,IUnit,IFaction). - File naming: kebab-case only (
legal-actions.ts, notlegalActions.ts). - Tests: co-locate as
*.test.tsalongside source; exercise public APIs; no network fixtures. - Class methods: arrow functions unless prototype method is required (no MobX stores in this package).
Adding a New Ruleset
- Create
src/rulesets/<name>/with:index.ts,constants.ts,types.ts,initial-state.ts,legal-actions.ts,reducer.ts,vision.ts,scoring.ts,events.ts. - Implement
IGameRuleset<TState, TAction, TView, TEvent>inindex.ts. - Export the ruleset object and all public types from
src/index.ts. - Do not import from other rulesets.
- Run
pnpm run check:depto verify the dependency graph remains clean.
