Game systems are optional modules that extend the simulation with additional behaviours. Each system implements the GameSystem interface and receives a SystemContext on every engine tick, giving it access to all agent runtime states, the global simulation state, and the event bus.
Without any systems, the simulation still renders agents and plays their animations based on the status values you emit from your adapter. Systems layer on top to add economics, morale, weather, awards ceremonies, and more.
Pass an array of instantiated system objects in config.systems:
import { EconomySystem, TimeSystem, ReputationSystem } from 'pixel-ops/systems';
const config = {
agents: [...],
systems: [
new EconomySystem(),
new TimeSystem(),
new ReputationSystem(),
],
};Systems execute in priority order on each game tick. Lower priority numbers run first.
If you want to create a custom system, implement this interface:
interface GameSystem {
name: string;
priority: number;
update(delta: number, context: SystemContext): void;
onEvent?(eventType: string, payload: unknown): void;
}
interface SystemContext {
agents: Map<string, AgentRuntimeState>;
state: GlobalSimState;
eventBus: GameEventBusType;
}| Field | Type | Description |
|---|---|---|
name |
string |
Identifier used in logs |
priority |
number |
Execution order; lower runs first |
update |
function |
Called every game tick with elapsed milliseconds |
onEvent |
function |
Optional — receives events from the internal event bus |
| Field | Description |
|---|---|
agents |
Map of agent ID to AgentRuntimeState. Mutate needs, moods, and stats here. |
state |
Global simulation state: reputation, economy, time, season, policies, room prestige |
eventBus |
Emit and subscribe to internal game events |
Priority: 55
Tracks compute credits as an in-simulation currency. Agents earn credits on task completion and spend them on upkeep per tick. Error recovery costs additional credits.
Events emitted:
credits-earned—{ amount: number, agentId: string, reason: string }credits-spent—{ amount: number, reason: string }budget-warning—{ credits: number }— fired when credits drop below 50
HUD wired to: BudgetBar component (top right area of canvas)
Usage: Useful when you want to model agent costs visually — e.g., token spend per LLM call, compute cost per inference job.
import { EconomySystem } from 'pixel-ops/systems';
new EconomySystem()You can also drive the economy directly from your adapter:
this.emit('economy:transaction', { amount: 100, type: 'income', reason: 'API call' });Priority: 3
Cycles the ambient weather between clear, cloudy, rain, snow, and storm. Weather transitions are weighted by the current season. Intervals range from 5 to 15 real minutes, shortened at higher time speeds.
Depends on: TimeSystem (for the current season)
Events emitted:
weather-changed—{ type: WeatherType, intensity: number, previous: WeatherType, season: string }
Usage: Purely cosmetic in v0.1.0. Can be used with a custom system that changes agent morale based on weather events.
import { WeatherSystem, TimeSystem } from 'pixel-ops/systems';
systems: [new TimeSystem(), new WeatherSystem()]Priority: 60
Running research agents accumulate research points each tick, with diminishing returns for multiple simultaneous researchers. Points are awarded to the first incomplete research topic in a fixed queue.
Built-in research topics:
| Topic ID | Name | Points required | Unlocks |
|---|---|---|---|
adv-monitoring |
Advanced Monitoring | 100 | Heatmap overlays |
agent-training |
Agent Training | 150 | Training room |
infra-v2 |
Infrastructure v2 | 200 | Server upgrade room |
social-intel |
Social Intelligence | 120 | Enhanced relationships |
stats-suite |
Statistics Suite | 80 | Statistics room |
Events emitted:
research-progress—{ topicId, topicName, progress, required, delta }research-completed—{ topicId, topicName, unlocks, category }
Usage: Assign agents the research sprite and set their status to working when they are actively running.
import { ResearchSystem } from 'pixel-ops/systems';
new ResearchSystem()Priority: 10
Tracks three needs for each agent on a 0–1 scale. Needs influence the MoodSystem and can trigger automatic room changes.
| Need | Depletes when | Restores when |
|---|---|---|
energy |
Agent is running |
Agent is idle or disabled |
morale |
Agent fails or errors | Agent succeeds, is idle |
focus |
Agent changes status | Agent is running or idle |
Events emitted:
agent-tired—{ agentId, energy }— when energy drops below the rest thresholdagent-rested—{ agentId, energy }— when energy recovers above 70%agent-morale-changed—{ agentId, morale, delta }
Usage: Pair with MoodSystem to show visible mood icons. Pair with a custom system to automatically route tired agents to the break room.
import { NeedsSystem, MoodSystem } from 'pixel-ops/systems';
systems: [new NeedsSystem(), new MoodSystem()]Priority: 20
Derives visible mood icons from each agent's needs, status, and recent history. Up to two icons are shown per agent at a time. Icons are re-evaluated every 500ms.
Mood icons and their conditions:
| Icon | Condition |
|---|---|
exhausted |
Energy < 15% |
frustrated |
Morale < 30% |
on-fire |
Success streak >= threshold |
tired |
Energy < 30% while running |
celebrating |
Recently went idle after a success streak |
processing |
Running with energy > 50% |
thinking |
Focus < 30% while running |
on-break |
Idle with energy < 60% |
confident |
Morale > 90% |
anxious |
Morale between 30% and 40% |
cold |
Idle for a long time |
Depends on: NeedsSystem
Events emitted:
mood-changed—{ agentId, moods: MoodIcon[] }
Priority: 30
Adjusts the campus reputation score (0–1000) based on task completions, failures, and agent crashes. Uses quadratic dampening so score changes become harder near the extremes.
Score tiers:
| Score | Tier |
|---|---|
| 800–1000 | Legendary |
| 600–799 | Excellent |
| 400–599 | Good |
| 200–399 | Struggling |
| 0–199 | Critical |
Events emitted:
reputation-changed—{ reputation, delta, reason, agentId? }reputation-milestone—{ reputation, tier }— fired when the tier changes
HUD wired to: ReputationHUD component (top left of canvas)
You can also drive reputation from your adapter:
this.emit('reputation:change', { delta: 25, reason: 'Deployment succeeded' });Priority: 2
Advances a simulation clock where 1 real minute equals 1 game hour at 1x speed. Tracks the time of day (0–24), day count, and season. The TimeControls HUD lets users pause or accelerate time (1x, 2x, 4x).
Season cycle: Each season lasts a configurable number of game days. Seasons advance: spring → summer → autumn → winter → spring.
Events emitted:
time-of-day-changed—{ timeOfDay, dayCount }— once per game hourseason-changed—{ season }
HUD wired to: TimeControls component (top centre of canvas)
Priority: 25
Plays short sprite animation micro-behaviours when an agent is idle. Each agent type has a curated set of behaviours (e.g., workers stretch and sip coffee, orchestrators pace and point, researchers flip pages).
Behaviours fire at random intervals between idle periods. Custom agent types without a defined behaviour set fall back to a generic stretch and look-around animation.
Events emitted:
idle-behavior-started—{ agentId, behavior: string }idle-behavior-ended—{ agentId }
Priority: 35
Evaluates room prestige scores based on placed furniture. Furniture is classified into four tiers — functional, comfort, decoration, and premium — and the sum determines a room's prestige value. Prestige is re-evaluated every 5 seconds.
Events emitted:
room-prestige-changed—{ roomId, prestige: number }
Usage: Use the furniture editor to place higher-quality items in a room to raise its prestige. Custom systems can respond to prestige changes to boost morale or reputation.
Priority: 45
Generates random simulation events that require player decisions. Events have deadlines; ignoring them results in a reputation penalty.
Event types:
| Type | Trigger | Description |
|---|---|---|
rush-job |
Random chance per tick | A client demands urgent delivery. Accept for quick reputation, or decline. |
bug-epidemic |
3+ agent errors within 60 seconds | Rapid agent crashes detected. Declare code freeze or quarantine affected agents. |
vip-audit |
Every N game hours | Senior stakeholder visits. Prepare a showcase or let them wander. |
infra-failure |
Very rare random chance | Critical infrastructure offline. Emergency patch or controlled shutdown. |
Events emitted:
simulation-event-created—{ event: SimulationEvent }simulation-event-resolved—{ event, delta, reason, reputation }
HUD wired to: FaxMachine component (bottom right of canvas)
Priority: 50
Tracks sentiment between agent pairs, updated every 10 seconds. Sentiment is a value between -1 (negative) and +1 (positive), updated by four rules:
- Orchestrators appreciate high-success-rate workers
- Workers are neutral or negative towards peers with unbalanced (very heavy) workloads
- Agents running simultaneously develop proximity rapport
- Shared failures build empathy
Relationships are ephemeral — they reset when the component unmounts.
Query methods available on the system instance:
const relSys = new RelationshipSystem();
relSys.getRelationship('agent-a', 'agent-b'); // AgentRelationship | null
relSys.getTopRelationships('agent-a', 5); // AgentRelationship[]Priority: 60
Tracks per-agent performance across weekly game time periods and awards trophies at week end. Four award categories:
| Category | Trophy name | Criteria |
|---|---|---|
productivity |
Top Producer | Most task successes this week |
reliability |
Flawless Record | Zero failures, most total runs |
speed |
Speed Demon | Best success rate (min 3 runs) |
endurance |
Iron Agent | Most total runs this week |
Events emitted:
award-granted—{ trophy: Trophy }ceremony-started—{ trophies: Trophy[], week: number }
HUD wired to: AwardsModal component — displayed when a ceremony is triggered
Depends on: TimeSystem (for game day count)
Priority: 99
Plays short audio tones using the browser's Web Audio API. No external audio files — all sounds are synthesised from oscillators. Runs last in the pipeline (priority 99) so it reacts to events from all other systems.
Audio events:
| Event | Sound |
|---|---|
reputation-changed |
Short 880 Hz sine beep |
emergency-started |
Two-tone sawtooth alarm |
award-granted |
Three-note ascending fanfare |
ceremony-started |
Four-note C-E-G-C fanfare |
Audio is rate-limited to one sound per 200ms to prevent overlap. All errors are swallowed silently — audio is non-fatal.
Control methods:
const soundSys = new SoundSystem();
soundSys.setMuted(true); // silence all sounds
soundSys.setVolume(0.5); // 0–1import type { GameSystem, SystemContext } from 'pixel-ops';
export class AlertOnErrorSystem implements GameSystem {
name = 'AlertOnErrorSystem';
priority = 40;
private prevStatus = new Map<string, string>();
update(_delta: number, ctx: SystemContext): void {
for (const [id, agent] of ctx.agents) {
const prev = this.prevStatus.get(id);
if (prev && prev !== 'error' && agent.status === 'error') {
ctx.eventBus.emit('notification-added', {
notification: {
id: `alert-${id}-${Date.now()}`,
priority: 'critical',
message: `Agent ${id} entered error state`,
agentId: id,
createdAt: Date.now(),
expiresAt: Date.now() + 30_000,
read: false,
},
});
}
this.prevStatus.set(id, agent.status);
}
}
onEvent(eventType: string, payload: unknown): void {
// Optionally react to events from other systems or the adapter
}
}Register it alongside built-in systems:
import { AlertOnErrorSystem } from './AlertOnErrorSystem';
config.systems = [
new ReputationSystem(),
new AlertOnErrorSystem(),
]