npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@balpal4495/world-engine

v1.0.0

Published

Portable simulation runtime - world state, event ledger, chronicle, and narrative context

Downloads

206

Readme

World Engine

Portable simulation runtime for building worlds with memory.

World Engine tracks entities, relationships, and events — then derives significance, causal chains, themes, and historical reports from them. Games are the primary use case, but it works anywhere you need a world that remembers what happened.

npm install @balpal4495/world-engine

The problem

Most simulations forget.

Event → Outcome → Forgotten

World Engine turns that into:

Event → Consequence → Historical Significance → Legacy

Every meaningful action becomes part of history.


Quick start

import { WorldEngine } from "@balpal4495/world-engine"

const world = new WorldEngine()

// Create entities
const hero = world.createEntity({ type: "hero", name: "Night Falcon" })
const villain = world.createEntity({ type: "villain", name: "Shadow Lord" })

// Create a relationship
world.createRelationship({ source: hero.id, target: villain.id, type: "rivals" })

// Advance time
world.tick()

// Record what happened
const event = world.recordEvent({
  actor: hero.id,
  action: "save_city",
  target: villain.id,
  outcome: "Shadow Lord driven back",
})

world.tick()

// Ask the chronicle what mattered
const chronicle = world.chronicle()
console.log(chronicle.getSignificantEvents())
console.log(chronicle.getThemes())
console.log(chronicle.generateReport())

Core concepts

Entities

Everything in a world is an entity — hero, villain, kingdom, city, army, player.

const city = world.createEntity({
  type: "city",
  name: "Ironhold",
  attributes: { population: 12000 },
})

world.getEntity(city.id)
world.destroyEntity(city.id)

Relationships

Entities connect to each other. Connections create context for events.

world.createRelationship({ source: hero.id, target: city.id, type: "protects" })
world.getRelationships(hero.id)
world.endRelationship(relationshipId)

Events

Events are the source of truth. They are immutable once recorded.

world.recordEvent({
  actor: hero.id,
  action: "defend",
  target: city.id,
  outcome: "Attack repelled",
  causedBy: previousEventId,   // optional causal link
  metadata: { damage: 40 },    // optional arbitrary data
})

Ticks

Time moves forward one tick at a time. Every entity, event, and relationship is stamped with the tick it occurred on.

world.tick()          // advance by one tick
world.currentTick     // read the current tick

Chronicle

Chronicle analyses the event ledger and answers: what mattered, why, and what caused what?

const chronicle = world.chronicle()

// Events ranked by significance
chronicle.getSignificantEvents()

// Full causal chain from any event
chronicle.getEventChain(eventId)

// Recurring themes across history
chronicle.getThemes()

// Complete historical report
chronicle.generateReport()

No AI required. All analysis is deterministic.


Rules

Rules enforce world-specific constraints before any action is committed.

const world = new WorldEngine()

world.addRule({
  name: "no-self-target",
  onEvent(proposal) {
    if (proposal.target === proposal.actor) {
      return { rule: "no-self-target", reason: "An entity cannot target itself." }
    }
  },
})

world.addRule({
  name: "no-hero-villain-alliance",
  onCreateRelationship(proposal, ctx) {
    const source = ctx.getEntity(proposal.source)
    const target = ctx.getEntity(proposal.target)
    if (source?.type === "hero" && target?.type === "villain" && proposal.type === "ally") {
      return { rule: "no-hero-villain-alliance", reason: "Heroes cannot ally villains." }
    }
  },
})

Violations throw a RuleViolationError with the full list of violations.

import { RuleViolationError } from "@balpal4495/world-engine"

try {
  world.recordEvent({ actor: hero.id, action: "fly", target: hero.id })
} catch (e) {
  if (e instanceof RuleViolationError) {
    console.log(e.violations) // [{ rule, reason }, ...]
  }
}

Rules are pure functions — they never modify state, only observe and block.


Archive

For long-running worlds, World Engine automatically compresses old history into archived eras, keeping the live ledger lean.

const world = new WorldEngine({
  eraLength: 100,       // compress every 100 ticks (default)
  liveWindowSize: 50,   // keep the last 50 ticks in full detail (default)
})

Retiring entities

When a character's story is complete, retire them. Their history is compressed into a legacy; the raw events are removed from the live ledger.

const legacy = world.retireEntity(hero.id)
// legacy.entity, legacy.significantEvents, legacy.dominantActions, legacy.summary

world.chronicle().getEntityLegacy(hero.id)
world.chronicle().getEntityLegacies()
world.chronicle().getArchivedEras()

Narrative

Narrative turns history into prose. AI is optional — there is always a deterministic template fallback.

const narrative = world.narrative()

// Template-based (no AI)
await narrative.generateReport(chronicle.generateReport())
await narrative.describeEvent(event)
await narrative.describeChain(chain)
await narrative.describeThemes(themes)

// AI-enriched
const narrative = world.narrative({
  llm: async (messages) => {
    // call your LLM provider here
    return await myLLM(messages)
  },
})

The simulation is fully functional if the LLM disappears. Only prose quality is affected.


Persistence

Save and load worlds with the built-in SQLite adapter (Node.js / Electron).

import { WorldEngine } from "@balpal4495/world-engine"
import { SqliteAdapter } from "@balpal4495/world-engine/sqlite"

const adapter = new SqliteAdapter("./saves/game.db")
// or: new SqliteAdapter(":memory:") for tests

// Save
adapter.save("slot-1", world.save())

// Load
const snap = adapter.load("slot-1")
if (snap) world.load(snap)

// List saves
adapter.list() // [{ id, tick, savedAt }, ...]

// Delete
adapter.delete("slot-1")

adapter.close()

The PersistenceAdapter interface is platform-agnostic — implement it for IndexedDB, AsyncStorage, or any other backend.

import type { PersistenceAdapter } from "@balpal4495/world-engine"

class MyAdapter implements PersistenceAdapter {
  save(id, snapshot) { /* ... */ }
  load(id) { /* ... */ }
  list() { /* ... */ }
  delete(id) { /* ... */ }
}

Save / load

Snapshots are plain JSON — safe to serialise, store, and transfer.

const snapshot = world.save()
// { version: 1, tick, entities, relationships, events, archivedEras, entityLegacies }

const json = JSON.stringify(snapshot)

const world2 = new WorldEngine()
world2.load(JSON.parse(json))

Design rules

  • No platform code in core. Runs identically in browser, Node, Electron, React Native.
  • No AI required. Significance, themes, causal chains — all deterministic.
  • AI never changes history. The Narrative layer only describes what the simulation recorded.
  • Events are immutable. Nothing in the engine modifies a recorded event.
  • Rules are pure. They observe state, never change it.

Development

npm test          # run all tests (vitest)
npm run typecheck # TypeScript check
npm run build     # build ESM + CJS + types to dist/

Platform support

| Platform | Core engine | SQLite adapter | |-----------------|-------------|----------------| | Node.js | ✓ | ✓ | | Electron | ✓ | ✓ | | Browser | ✓ | — | | React Native | ✓ | — | | Bun / Deno | ✓ | ✓ (via compat) |