Universe-as-Code

axiom.compile

axiom.compile — Universe-as-Code: compiling a source tree into a .db cache.

A universe is defined as a versionable tree of text files (TOML/MD); the SQLite .db is only a compiled cache derived from the text. The text is the source of truth. This module reads the tree and populates a runtime .db.

Zero Qt dependency: pure engine, drivable from the CLI (axiom compile).

Boundary: only the definition tables are produced here. The runtime/save tables (Saves, Event_Log, State_Cache, Snapshots, Timeline, …) stay empty in the cache — they do not belong to the universe definition.

TOML reading: tomllib (stdlib). Writing (decompile) uses tomlkit.

exception axiom.compile.CompileError[source]

Universe compilation error (malformed source, missing required field).

axiom.compile.hash_directory(src_dir)[source]

Stable hash of the source content (relative path + bytes of each file).

Parameters:

src_dir (Path)

Return type:

str

axiom.compile.compile_universe(src_dir, output_db=None, force=False)[source]

Compile a source tree into a runtime SQLite database.

Parameters:
  • src_dir (str | Path) – Universe source folder (contains universe.toml).

  • output_db (str | Path | None) – Path of the .db to produce. Defaults to <src_dir>/.axiom-cache/universe.db.

  • force (bool) – Recompile even if the source hash is unchanged.

Returns:

The path of the compiled .db.

Return type:

Path

axiom.decompile

axiom.decompile — Universe-as-Code: decompiling a .db into a source tree.

The inverse of axiom.compile: reads an existing SQLite universe and regenerates the equivalent tree of text files (TOML/MD). Also used to migrate v1 .db universes to the versionable text format.

Preserves: entity_ids, rule_ids, calendar, full lore, locations + connections, scheduled events, story setup, item & stat definitions, personas.

TOML writing: tomlkit (keeps the formatting clean, helpful for human editing). Zero Qt dependency.

exception axiom.decompile.DecompileError[source]

Universe decompilation error.

axiom.decompile.read_definition(db_path)[source]

Read all the definition tables of a universe into normalised structures.

Never reads the runtime/save tables. Used by the decompilation and by the round-trip tests (semantic comparison).

Parameters:

db_path (str | Path)

Return type:

dict[str, Any]

axiom.decompile.decompile_universe(db_path, output_dir)[source]

Decompile a .db universe into a text source tree.

Parameters:
  • db_path (str | Path) – Path of the universe .db to read.

  • output_dir (str | Path) – Destination folder (created if missing).

Returns:

The path of the generated source folder.

Return type:

Path

axiom.package

axiom.package — Universe-as-Code: .axiom v2 packaging + v1 compat.

A .axiom v2 archive is a zip of the source tree (TOML/MD) including the compiled cache .axiom-cache/universe.db for instant loading.

Backward compatibility: old .axiom v1 archives (zip of JSON files) are detected and converted on the fly (v1 -> .db -> decompile -> v2 tree -> recompile).

Zero Qt dependency: pure engine. The Qt worker import_export_worker.py is a thin shell calling these functions.

exception axiom.package.PackageError[source]

Error while packing/unpacking a .axiom archive.

axiom.package.pack_universe(src_dir, output_path)[source]

Pack a source tree into a .axiom v2 archive.

Recompiles the cache (.axiom-cache/universe.db) first so it ships in the archive, then zips the tree. An archive only publishes the definition (same contract as exporting a flat .db, TICKET-039):

  • .git/ and the WAL sidecars (-wal/-shm) are excluded;

  • the embedded cache is a copy purged of the runtime tables (legacy embedded saves — private play history — do not travel).

Parameters:
  • src_dir (str | Path) – Universe source folder (contains universe.toml).

  • output_path (str | Path) – Path of the .axiom archive to produce.

Returns:

The path of the created archive.

Return type:

Path

axiom.package.export_db_to_axiom(db_path, output_path)[source]

Export a .db universe to a .axiom v2 archive (decompile -> pack).

For universes that still only exist as a .db (legacy GUI). The archive only contains the definition (the decompiled tree + its recompiled cache): saves embedded in the .db are not exported — same contract as the v1 export.

Parameters:
  • db_path (str | Path) – Path of the universe .db to export.

  • output_path (str | Path) – Path of the .axiom archive to produce.

Returns:

The path of the created archive.

Return type:

Path

axiom.package.detect_format(axiom_path)[source]

Return ‘v2’ or ‘v1’, or raise PackageError, depending on the archive content.

Parameters:

axiom_path (str | Path)

Return type:

str

axiom.package.unpack_universe(axiom_path, dest_root)[source]

Unpack a .axiom (v1 or v2) into a playable source tree.

v2: unzip, check the hash — keep the embedded .db if valid, recompile otherwise. v1: convert (JSON -> .db -> decompile -> v2 tree -> compile).

Parameters:
  • axiom_path (str | Path) – Path of the .axiom archive.

  • dest_root (str | Path) – Root folder where the universe is materialised (in a <name>/ subfolder).

Returns:

The path of the universe source folder (containing universe.toml + cache).

Return type:

Path

axiom.library

axiom.library — Universe-as-Code: discovery of installed universes.

The Hub historically listed flat *.db files in ~/AxiomAI/universes/. With Universe-as-Code, an imported universe (.axiom v2) or one created in source mode lives in a folder universes/<name>/ (universe.toml + .axiom-cache/universe.db). This module provides the unified discovery of both forms, engine-side (zero Qt) — the Hub’s DbWorker is just a shell on top.

exception axiom.library.LibraryError[source]

Universe library management error.

axiom.library.universe_root_for(db_path)[source]

Return a universe’s source folder when db_path is its compiled cache.

universes/<name>/.axiom-cache/universe.db maps to universes/<name>/. Returns None for a flat .db (legacy) or any other path.

Parameters:

db_path (str | Path)

Return type:

Path | None

axiom.library.discover_universes(library_dir)[source]

List the playable universes of a library folder.

Two forms are recognised:

  • flat *.db (legacy);

  • a subfolder containing universe.toml (Universe-as-Code), compiled on demand when the cache is missing/stale (no-op when the hash is unchanged).

A malformed source folder is skipped (warning logged): it must not prevent the Hub from showing the rest of the library.

Returns:

List of dicts {db_path, source_dir, name, last_updated, difficulty}, sorted by file/folder name. source_dir is None for a flat .db.

Parameters:

library_dir (str | Path)

Return type:

list[dict]

axiom.library.diff_source_trees(before_dir, after_dir)[source]

Text diff between two source trees (protected areas excluded).

Used for previews: before_dir is the real source, after_dir a temporary tree reflecting what the source would become.

Returns:

A list of dicts with keys path, status (‘added’ | ‘modified’ | ‘removed’) and diff — a unified diff, sorted by path.

Parameters:
Return type:

list[dict]

axiom.library.apply_staged_source(staged_dir, src_dir, db_path)[source]

Apply a validated source tree: mirror it to the source + recompile.

staged_dir is a full snapshot of the future source (produced by the preview sandbox): it replaces the source — protected areas (.axiom-cache/, .git/) untouched — then the definition is recompiled in-place into db_path (runtime intact).

Parameters:
Return type:

Path

axiom.library.convert_flat_db_to_folder(db_path)[source]

Convert a flat legacy .db into a Universe-as-Code folder universe.

  • the definition is decompiled to <parent>/<stem>/ (same universe key: the saves keep their saves/<stem>/ folder);

  • every embedded save is extracted to its own separate file, then linked to the new source (resynchronised on open);

  • the source is compiled (.axiom-cache/universe.db cache);

  • the original is kept as <name>.db.bak (manual recovery stays possible, and it disappears from the Hub discovery — no duplicate).

Returns:

A dict with keys source_dir and db_path — db_path is the new cache.

Parameters:

db_path (str | Path)

Return type:

dict

axiom.library.sync_source_if_any(db_path)[source]

After a definition write (Creator Studio, Populate): when the .db is the cache of a folder universe, rewrite the text tree so it stays the source of truth (TICKET-027).

Never raises: a Studio save must not fail because the text mirror failed (warning logged).

Returns:

True when a source was resynchronised.

Parameters:

db_path (str | Path)

Return type:

bool

axiom.library.sync_source_from_db(db_path, src_dir)[source]

Rewrite a universe’s source tree from its .db (mirror).

Decompiles to a temporary folder then aligns src_dir on it: files are added/overwritten, orphan files removed (definition removed in the Studio also disappears from the text). .axiom-cache/ and .git/ are never touched. Entities with origin=’runtime’ (the player, NPCs born in game) are not exported to the source.

The cache hash is updated: the .db that was just written IS the compilation of the freshly rewritten source (lossless round-trip).

Parameters:
Return type:

None

axiom.dev

axiom.dev — Universe-as-Code: dev mode with hot reload.

axiom dev <src_dir> watches the source tree and, on every change, recompiles the definition into the target .db. The real “modder” mode: edit entities/bob.toml in an editor and see the effect on the next turn.

Key point: legacy saves may live in the same `.db` as the definition. A full compile_universe rebuilds the file from scratch and would destroy games in progress. Hot reload therefore goes through refresh_definition: an in-place update of the definition tables only, in a single transaction, runtime tables untouched (Saves, Event_Log, State_Cache, Snapshots, Timeline, Items_Inventory, Active_Modifiers, Fired_Scheduled_Events).

FK constraint: three definition tables have runtime children with ON DELETE CASCADEEntities (Items_Inventory, Active_Modifiers), Item_Definitions (Items_Inventory), Scheduled_Events (Fired_Scheduled_Events). For those, the sync uses targeted UPDATE/INSERT/DELETE statements (a delete-all-then-reinsert would purge the children of kept rows). A row removed from the source loses its runtime children: that is intended (the text is the truth).

Zero Qt dependency: pure engine, drivable from the CLI. The watch is a hash_directory polling loop (no watchdog dependency; universe trees are small, hashing is instant).

axiom.dev.refresh_definition(src_dir, db_path=None)[source]

Recompile the universe definition into an existing .db, in place.

Runtime/save tables are not touched. When the .db does not exist yet, this is equivalent to compile_universe(force=True).

Parameters:
  • src_dir (str | Path) – Universe source folder (contains universe.toml).

  • db_path (str | Path | None) – Target .db. Defaults to <src_dir>/.axiom-cache/universe.db.

Returns:

The path of the refreshed .db.

Raises:

CompileError – malformed source (the .db is left unchanged, the transaction is rolled back).

Return type:

Path

axiom.dev.ensure_compiled(src_dir, db_path=None)[source]

Guarantee an up-to-date compiled cache without ever destroying saves.

Use this wherever a folder universe must be made playable (Hub, axiom play): missing cache -> full compilation; stale cache -> in-place refresh_definition (a full compile_universe would rebuild the file and erase any saves it contains); fresh cache -> no-op.

Returns:

The path of the playable .db.

Parameters:
Return type:

Path

axiom.dev.poll_once(src_dir, db_path, last_hash)[source]

One watch iteration: recompile when the source changed since last_hash.

Parameters:
  • src_dir (str | Path) – Watched source folder.

  • db_path (str | Path | None) – Target .db (None = default cache).

  • last_hash (str | None) – Hash from the previous iteration (None = first pass).

Returns:

Tuple (current hash, True when a refresh was performed).

Raises:

CompileError – the source changed but is malformed. The current hash is carried by the exception (src_hash attribute) so the caller can record it and avoid retrying the same content in a loop.

Return type:

tuple[str, bool]

axiom.dev.watch_universe(src_dir, db_path=None, interval=1.0, on_event=<built-in function print>, should_stop=None)[source]

Watch the source tree and recompile the definition on every change.

A momentarily malformed source (mid-typing save, invalid TOML) is reported through on_event but does not kill the loop: the next fix triggers a refresh again.

Parameters:
  • src_dir (str | Path) – Universe source folder.

  • db_path (str | Path | None) – Target .db (None = <src>/.axiom-cache/universe.db).

  • interval (float) – Polling period in seconds.

  • on_event (Callable[[str], None]) – Log callback (one line per event).

  • should_stop (Callable[[], bool] | None) – Optional predicate tested at each iteration (for tests / integration); None = loop until KeyboardInterrupt.

Return type:

None