Skip to main content

🌍 pykworldsim v3

Production-grade ECS world simulation framework.
Simulate people, cities, relationships, jobs, goals, and events with a clean, fast, fully-tested Entity-Component-System engine.

Python 3.10+ Version License: MIT Tests


✨ What's new in v3

Feature Detail
⚡ Inverted-index QueryEngine O(smallest-set) lookups — no full entity scans
🔒 Staged mutation queues stage_add_component / destroy_entity safe during iteration
🎲 Injected RNG sim.rng = random.Random(seed) — fully deterministic
📡 EventBus emit() / subscribe() — systems talk via events, not direct calls
🔃 System priority ordering system.priority — lower runs first, sorted automatically
🪝 Lifecycle hooks on_add / on_remove via EntityRegistry + EventBus
📸 Snapshot / Replay sim.take_snapshot("label") / sim.restore_snapshot("label")
🔌 Plugin registry Register custom systems by class or dotted import path
⚙️ Config: world.size + count count: N spawns N identical entities; world.size wired
🖥️ CLI overrides --steps --dt --seed --debug override config file values

📦 Installation

# From PyPI
pip install pykworldsim

# From source (dev extras)
git clone https://github.com/ka1rav6/pykworldsim
cd pykworldsim
pip install -e ".[dev]"

🚀 Quick Start

Physics

from pykworldsim import World, Simulation, Position, Velocity, MovementSystem
from pykworldsim.core.systems.physics import PhysicsSystem

world = World(name="demo")
world.register_system(PhysicsSystem(gravity=9.81, bounds=(0, 0, 100, 100)))
world.register_system(MovementSystem())

e = world.create_entity()
world.add_component(e, Position(x=10.0, y=0.0))
world.add_component(e, Velocity(dx=2.0, dy=0.0))

sim = Simulation(world, seed=42)
sim.run(steps=100, dt=0.1)
print(world.get_component(e, Position))

Social world

from pykworldsim import (World, Simulation, Person, Job, Goal,
                          SocialSystem, EventSystem, EventComponent)

world = World(name="society")
world.register_system(SocialSystem())
world.register_system(EventSystem())

# React to goal completion via EventBus
world.events.subscribe("goal_completed",
    lambda d: print(f"Goal done: {d['description']}"))

alice = world.create_entity()
world.add_component(alice, Person(name="Alice", age=28.0, happiness=65.0))
world.add_component(alice, Job(title="Engineer", salary=8000.0, satisfaction=75.0))
world.add_component(alice, Goal(description="Get promoted", priority=9.0))

sim = Simulation(world, seed=2024)
sim.run(steps=20, dt=1.0)
p = world.get_component(alice, Person)
print(f"Alice: age={p.age:.1f}, happiness={p.happiness:.1f}")

Deterministic RNG

sim = Simulation(world, seed=42)
# All randomness in systems must use sim.rng (not global random)
value = sim.rng.random()   # deterministic across runs

Snapshot / Replay

sim.run(steps=50, dt=0.1)
sim.take_snapshot("checkpoint")      # save state
sim.run(steps=50, dt=0.1)           # advance further
sim.restore_snapshot("checkpoint")  # roll back

Staged mutations (safe during iteration)

class SpawnSystem(BaseSystem):
    def update(self, world, dt):
        for entity, (pos,) in world.get_entities_with(Position):
            if some_condition:
                new_e = world.create_entity()
                # Stage the component — applied after all systems finish
                world.stage_add_component(new_e, Position(x=0.0, y=0.0))

🖥️ CLI

# Basic run
pykworldsim run examples/config.yaml

# Override config values
pykworldsim run examples/config.yaml --steps 200 --dt 0.05 --seed 99 --debug

# Save final state
pykworldsim run examples/config.yaml --save output.json

# Inspect a saved world
pykworldsim inspect output.json

# Load a custom plugin
pykworldsim run config.yaml --plugin mypackage.systems.MySystem

# List registered plugins
pykworldsim plugins

⚙️ YAML / JSON Config

name: my-world
log_level: INFO

world:
  size: 200.0

simulation:
  steps: 100
  dt: 0.1
  seed: 42

systems:
  - type: MovementSystem
  - type: SocialSystem
    params:
      age_rate: 0.05
      energy_decay: 0.3
  - type: PhysicsSystem
    params:
      gravity: 9.81
      bounds: [0, 0, 200, 200]

entities:
  # Single entity
  - position: {x: 0.0, y: 0.0}
    velocity:  {dx: 5.0, dy: 2.0}

  # Spawn 10 identical entities
  - position: {x: 50.0, y: 50.0}
    velocity:  {dx: 1.0, dy: 0.0}
    count: 10

  # Person with job + goal
  - person:   {name: Alice, age: 28.0, happiness: 65.0, energy: 90.0}
    job:      {title: Engineer, salary: 8000.0, satisfaction: 75.0}
    goal:     {description: "Get promoted", priority: 9.0}
    position: {x: 25.0, y: 25.0}

📡 EventBus

Systems communicate via events, never direct calls:

# Subscribe anywhere
world.events.subscribe("goal_completed", lambda d: print(d))
world.events.subscribe("entity_created", lambda d: log(d["entity"]))
world.events.subscribe("entity_destroyed", lambda d: cleanup(d["entity"]))

# Emit immediately
world.events.emit("custom_event", {"key": "value"})

# Emit deferred (safe inside system.update — dispatched after tick)
world.events.emit_deferred("score_changed", {"delta": +10})

# Built-in events
# "entity_created"   → {"entity": Entity}
# "entity_destroyed" → {"entity": Entity}
# "goal_completed"   → {"entity": Entity, "description": str}

🔌 Plugin System

from pykworldsim.core.systems.base_system import BaseSystem
from pykworldsim.plugins import PluginRegistry

class BoundaryWrapSystem(BaseSystem):
    priority: int = 15   # runs after MovementSystem(10), before SocialSystem(20)

    def __init__(self, width=100.0, height=100.0):
        super().__init__()
        self.width = width
        self.height = height

    def update(self, world, dt):
        for entity, (pos,) in world.get_entities_with(Position):
            pos.x %= self.width
            pos.y %= self.height

# Register by class
PluginRegistry.register("BoundaryWrapSystem", BoundaryWrapSystem)

# Register by dotted path (for CLI --plugin flag)
PluginRegistry.register_from_path("mypackage.systems.BoundaryWrapSystem")

🏗️ Architecture

pykworldsim/
├── core/
│   ├── entity.py          Thread-safe registry + lifecycle hooks (on_add/on_remove)
│   ├── world.py           ECS container — staged queues, query engine, EventBus
│   ├── simulation.py      Deterministic loop — injected RNG, snapshot/replay
│   ├── query.py           Inverted-index QueryEngine — O(smallest-set) lookups
│   ├── events.py          EventBus — emit/subscribe, immediate + deferred dispatch
│   ├── components/
│   │   ├── position.py       2-D coordinate
│   │   ├── velocity.py       2-D velocity
│   │   ├── person.py         Individual agent
│   │   ├── location.py       Named place / city
│   │   ├── relationship.py   Directed bond
│   │   ├── job.py            Occupation
│   │   ├── goal.py           Motivation / objective
│   │   └── event_component.py  Scheduled occurrence
│   ├── systems/
│   │   ├── base_system.py    Abstract base — priority + update(world, dt) contract
│   │   ├── movement.py       priority=10 — pos += vel × dt
│   │   ├── physics.py        priority=5  — gravity + boundary bounce
│   │   ├── social.py         priority=20 — ageing, energy, happiness, goals
│   │   └── event_system.py   priority=1  — scheduled event dispatch
│   └── config/
│       └── loader.py         YAML/JSON → World + Simulation
├── plugins/
│   └── registry.py           Dynamic system registration
├── utils/
│   └── logging.py            Structured logging helper
└── cli.py                    Typer CLI — run/inspect/plugins + all overrides

ECS contract

Concept Role Rule
Entity Opaque integer ID Carries no data
Component @dataclass — data only No logic; implements to_dict / from_dict
System update(world, dt) Uses world.get_entities_with(...) only — never touches world internals
World Container + staged queue Applies mutations after all systems finish
EventBus Pub/sub messaging Systems emit, never call each other directly

System execution order (default priorities)

tick N:
  EventSystem     priority=1   (fire scheduled events first)
  PhysicsSystem   priority=5   (apply gravity/forces)
  MovementSystem  priority=10  (integrate velocity → position)
  SocialSystem    priority=20  (age, happiness, goals)
  → _flush_staged()            (apply deferred adds/removes/destroys)
  → events.flush()             (dispatch deferred EventBus messages)

🧪 Tests

pip install -e ".[dev]"

pytest                                  # all tests
pytest --cov=pykworldsim               # with coverage
pytest tests/test_simulation.py -v     # single file
pytest -k "test_determinism"           # filter by name

Test modules:

File Covers
test_entity.py Entity creation, lifecycle hooks, thread safety
test_query.py QueryEngine — add/get/has/remove/query
test_events.py EventBus — emit/subscribe/deferred/flush
test_world.py World — staged queues, priority ordering, report
test_simulation.py Lifecycle, determinism, injected RNG, snapshot
test_systems.py All 4 systems + system contract
test_save_load.py Serialisation, ConfigLoader, edge cases
test_plugins.py PluginRegistry — register/unregister/path

📄 License

MIT © Kairav Dutta

NOTE:

I have majorly used AI to create this. The idea and the way of implementation is original.

Metadata

Release files for pykworldsim 3.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pykworldsim 3.0.0
File Size Uploaded
pykworldsim-3.0.0.tar.gz 36.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pykworldsim 3.0.0
File Interpreter ABI Platform
pykworldsim-3.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 72.9 kB

Release files / pykworldsim-3.0.0.tar.gz

Download URL pykworldsim-3.0.0.tar.gz
Size 36.2 kB
Tags Source
SHA-256 checksum
How to use checksums
30e31755798007267089e4c132f0965acc8e2e6c81585b38d9f95603df413324
BLAKE2b-256 checksum
How to use checksums
8a053e75e8c7816000be7d92636e6d27ca9e21c556458cb0a7560fb95f3bbbf8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / pykworldsim-3.0.0-py3-none-any.whl

Download URL pykworldsim-3.0.0-py3-none-any.whl
Size 36.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
010e07af0b0b095d08e37ec10292c52aa9e14532bffc31529786e9bb485efeb5
BLAKE2b-256 checksum
How to use checksums
0fbfc4245c060560b052f5710391c661f82b0cc216942718819b04a9a9d933fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

3.0.0 This release

2 release files

2.0.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page