Skip to main content

foliot

foliot is a small, deterministic simulation engine for worlds that advance in logical ticks.

It gives you a durable action queue, scheduled and recurring work, reproducible randomness, suspension and resumption, atomic ticks, real-time pacing, and an optional layer for simultaneous multi-entity events. It deliberately does not define characters, maps, combat, health, inventories, or quests. Those belong to your simulation.

Status: pre-alpha. The core is tested and usable, but the public API may still change before 1.0.

Why foliot?

  • Reproducible: the same world seed and inputs produce the same history.
  • Order-independent: unrelated actions have independent random streams.
  • Transactional: queue changes, effects, journal lines, and the tick commit through one store transaction.
  • Fast-forwardable: use logical time to run millions of ticks without sleeping.
  • Domain-agnostic: your game owns its world model and rules.
  • Storage-agnostic: implement two small protocols for PostgreSQL, MySQL, MariaDB, a file, or another backend.
  • Dependency-free: the installed library uses only the Python standard library.

Installation

pip install foliot

Python 3.12 or newer is required.

Five-minute example

from dataclasses import dataclass
from typing import override

from foliot import BaseAction, EntityId, ManualDriver, MemoryStore, Simulation, TickContext


@dataclass(slots=True)
class World:
    energy: int = 100


@dataclass(frozen=True, slots=True)
class LoseEnergy:
    amount: int

    def apply(self, world: World, /) -> None:
        world.energy -= self.amount


class Hunger(BaseAction[World]):
    def __init__(self, entity_id: EntityId) -> None:
        super().__init__(entity_id, suspendable=False)

    @override
    def process(self, ctx: TickContext[World], /) -> None:
        ctx.emit(LoseEnergy(1))
        ctx.log("Lira grows hungry.")


world = World()
store = MemoryStore(
    world,
    world_seed=1,
    initial_actions=((Hunger(EntityId("lira")), None),),
)

Simulation(store).run(ManualDriver(until_tick=9))

assert world.energy == 90
assert len(store.logs) == 10

due_tick=None makes Hunger recurring, so it runs once per logical tick. Effects and journal lines are collected first and applied only after every due action has made its decision.

The model

An action begins unbound. Its first successful store admission assigns one permanent sequence number and an active state. Scheduling the same object again changes its deadline without changing that identity.

Request Meaning
ctx.schedule(action, 100) Run at tick 100.
ctx.schedule(action, None) Run every tick until ctx.finish().
ctx.emit(effect) Apply a game-defined world change after decisions.
ctx.log(line) Append deterministic narrative output.
ctx.suspend(entity_id, by=handle) Pause that entity's suspendable actions.
ctx.finish() Remove the current action.

Concrete deadlines must always be later than the current tick. An action due at tick 90 can reschedule itself for tick 100; it keeps the same sequence number.

Optional simultaneous Events

Ordinary actions are enough for walking, hunger, poison, construction, growth, and most other simulation work. Import foliot.events only when several entities must decide from the same tick-start state before any result applies.

from foliot import Simulation
from foliot.events import EventMemoryStore, Events

store = EventMemoryStore(world, world_seed=1)
simulation = Simulation(store, events=Events(store))

Each EventAction returns one game-defined Intent. Once every expected participant has answered in the same tick, the concrete BaseEvent resolves the complete set into either Outcome.continue_with(...) or Outcome.end(...). Combat formulas and lifecycle rules remain game code.

Run the complete example:

uv run python -m examples.eventworld

Storage

MemoryStore and EventMemoryStore are ready-made implementations for tests, examples, and temporary simulations. They disappear with the process.

A durable application implements Store and Txn (plus EventStore and EventTxn when Events are enabled). Foliot decides when the transaction begins and commits; your adapter decides how actions and world state are encoded in its database.

See Writing a store for the complete contract and a PostgreSQL-shaped example.

Documentation

Development

uv sync
uv run ruff format --check src tests examples
uv run ruff check src tests examples
uv run basedpyright
uv run pytest

License

MIT

Release files for foliot 0.1.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 foliot 0.1.0
File Size Uploaded
foliot-0.1.0.tar.gz 62.5 kB Details

Built distribution (wheel)

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

Total release size: 98.8 kB

Release files / foliot-0.1.0.tar.gz

Download URL foliot-0.1.0.tar.gz
Size 62.5 kB
Tags Source
SHA-256 checksum
How to use checksums
c3f7e33caca5655e4f41d71cfa1fff8257b2647f7d61d30768f076d8444c8321
BLAKE2b-256 checksum
How to use checksums
901019455932c4314ea4bcd0818d0634c915af2c484d4c45d3b177ae233e0710
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / foliot-0.1.0-py3-none-any.whl

Download URL foliot-0.1.0-py3-none-any.whl
Size 36.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
631bd8a91de83cb2556e44a28c6f5999dbded7a5d1c140ec24093126b1f3b090
BLAKE2b-256 checksum
How to use checksums
801494aaf6d978fc6e8eea7232392640c87686bdc98fb843308c770deba80f5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

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