Egregoria Python Mod API
Stable API version 1 for Egregoria Android 0.6.1-android.1. Mods are ordinary ZIP archives containing mod.json and UTF-8 Python source executed by embedded RustPython 0.5.
Stable releaseHello sampleDependency/action sample
Trust boundary: enabled Python mods execute inside the game process. Archive validation protects paths/storage and explicit approval protects code replacement; it does not sandbox trusted Python code or impose a hard CPU/memory quota.
Install and lifecycle
- Create the ZIP using the structure below.
- In Egregoria, open Main Menu → Python Mods → Import ZIP.
- Select the ZIP with Android's system picker.
- Review the discovered manifest, then explicitly enable it.
- Restart Egregoria. Enabled mods execute once during startup in deterministic dependency order.
Import never executes a selected archive. Approval is bound to (manifest ID, archive SHA-256); changing even one archive byte disables it until approved again. Duplicate installed manifest IDs invalidate every conflicting archive.
ZIP layout
my-mod.zip
├── mod.json
├── main.py
└── assets/
└── optional.jsonThe manifest may instead live inside exactly one wrapper directory; entrypoint and asset paths are relative to that manifest.
Required Python entrypoint
def on_load(context):
context.log("Loaded " + context.mod_name)
count = int(context.get_data("count", "0")) + 1
context.set_data("count", count)on_load(context) must exist and be callable. There are no per-frame callbacks in API 1.
mod.json
{
"api": 1,
"id": "starter-funds",
"name": "Starter Funds",
"version": "1.0.0",
"description": "Adds money once.",
"compatible_game_version": "0.6.1",
"entrypoint": "main.py",
"dependencies": ["hello-egregoria"],
"load_after": [],
"load_before": [],
"assets": []
}| Field | Required/default | Contract |
|---|---|---|
api | Required | Integer; must be 1. |
id | Required | 1–64 lowercase ASCII letters, digits, dot, underscore, or hyphen. Stable storage/dependency identity. |
name | Required | Human-readable, 1–80 characters. |
version | Required | 1–32 characters; author-defined. |
description | "" | Up to 500 characters. |
compatible_game_version | "*" | * or exact game version, up to 32 characters. |
entrypoint | "main.py" | Safe relative path to UTF-8 source. |
dependencies | [] | Required enabled mod IDs. Missing, disabled, cyclic, or runtime-failed dependencies prevent execution. |
load_after | [] | Ordering edges for enabled installed IDs. |
load_before | [] | Inverse ordering edges for enabled installed IDs. |
assets | [] | Up to 128 safe relative paths that must exist. API 1 does not expose an asset-reading context method. |
Dependency/order arrays allow up to 64 unique valid IDs each. Ordering is deterministic and lexical when several nodes are otherwise ready.
Context metadata
| Attribute | Type | Meaning |
|---|---|---|
context.api_version | int | Always 1. |
context.mod_id | str | Manifest ID. |
context.mod_name | str | Manifest name. |
context.mod_version | str | Manifest version. |
context.game_version | str | Current Egregoria base version. |
Context methods
context.log(message)
Adds a diagnostic line shown in Python Mods. Values are converted with str(). Maximum 128 lines per load; individual messages are truncated after 1,024 characters.
context.get_data(key, default=None)
Reads this mod's private persistent string state. The key is converted to a string.
context.set_data(key, value)
Writes private persistent state after successful execution. Key/value are strings. Limits: key 128 characters, value 4,096 characters, 256 entries. U+001F is forbidden. If execution fails, returned actions/state are not committed.
context.add_money(amount)
Queues a signed whole-bucks startup delta. Allowed range: -1,000,000,000 through 1,000,000,000. Final host arithmetic is saturating.
context.set_time_warp(multiplier)
Queues startup simulation speed. Allowed values: 0, 1, 2, 4, 8.
At most 128 total actions may be queued in one execution. The Rust host validates every action again after Python returns.
Complete examples
Persistent load counter
def on_load(context):
count = int(context.get_data("load_count", "0")) + 1
context.set_data("load_count", count)
context.log("Python runtime active; load count = " + str(count))
One-time starter funds
def on_load(context):
if context.get_data("granted", "no") != "yes":
context.add_money(50000)
context.set_data("granted", "yes")
context.log("Granted 50,000 starter funds")Archive validation
- Archive file: maximum 16 MiB and 256 entries.
- Actual decompressed output: maximum 2 MiB per entry and 8 MiB total.
- Expansion ratio: maximum 200:1.
- Actual decompressed sizes must match ZIP declarations; forged metadata is rejected.
- Encrypted entries, unsupported compression, symlinks, and special files are rejected.
- Absolute paths, drive paths, empty components,
.,.., NUL, and colon components are rejected. - Duplicate normalized paths and case-fold collisions are rejected.
- Paths longer than 240 characters are rejected.
- Validation, hashing, and extraction consume one bounded app-private snapshot; extraction is atomically promoted into a versioned SHA-256 cache.
Runtime/storage details
files/mods/ installed ZIPs files/config/python_mods.json ID → approved SHA-256 files/config/python_mod_cache/ID/SHA256/ validated extraction cache files/config/python_mod_state/ID.json private persistent string state
RustPython is embedded without its standard library. Native CPython extensions and packages requiring CPython are unsupported. Each mod gets a fresh interpreter/scope and isolated diagnostics, but all code remains trusted in-process code.
Compatibility policy
API 1 is the contract documented on this page. Internal globals and implementation details beginning with underscores are not API and may change without notice. Use only the public context attributes and methods above.